diff --git a/Cargo.lock b/Cargo.lock index f781e4c2..a6753e23 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -190,9 +190,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.80" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1" +checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" [[package]] name = "approx" @@ -713,9 +713,9 @@ dependencies = [ [[package]] name = "async-io" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f97ab0c5b00a7cdbe5a371b9a782ee7be1316095885c8a4ea1daf490eb0ef65" +checksum = "dcccb0f599cfa2f8ace422d3555572f47424da5648a4382a9dd0310ff8210884" dependencies = [ "async-lock 3.3.0", "cfg-if", @@ -784,7 +784,7 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e47d90f65a225c4527103a8d747001fc56e375203592b25ad103e1ca13124c5" dependencies = [ - "async-io 2.3.1", + "async-io 2.3.2", "async-lock 2.8.0", "atomic-waker", "cfg-if", @@ -2789,7 +2789,7 @@ dependencies = [ "regex", "syn 2.0.52", "termcolor", - "toml 0.8.10", + "toml 0.8.11", "walkdir", ] @@ -4228,7 +4228,7 @@ version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6b0422c86d7ce0e97169cc42e04ae643caf278874a7a3c87b8150a220dc7e1e" dependencies = [ - "async-io 2.3.1", + "async-io 2.3.2", "core-foundation", "fnv", "futures", @@ -4662,11 +4662,15 @@ dependencies = [ "pallet-balances", "pallet-burner", "pallet-collator-selection", + "pallet-communities", "pallet-message-queue", "pallet-multisig", + "pallet-nfts", "pallet-payments", "pallet-preimage", "pallet-proxy", + "pallet-referenda", + "pallet-referenda-tracks", "pallet-scheduler", "pallet-session", "pallet-sudo", @@ -6515,6 +6519,30 @@ dependencies = [ "sp-std 8.0.0", ] +[[package]] +name = "pallet-communities" +version = "0.1.0" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "pallet-assets", + "pallet-balances", + "pallet-nfts", + "pallet-preimage", + "pallet-referenda", + "pallet-referenda-tracks", + "pallet-scheduler", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std 8.0.0", + "staging-xcm", + "virto-common", +] + [[package]] name = "pallet-conviction-voting" version = "4.0.0-dev" @@ -6771,6 +6799,24 @@ dependencies = [ "sp-std 8.0.0", ] +[[package]] +name = "pallet-nfts" +version = "4.0.0-dev" +source = "git+https://github.com/virto-network/polkadot-sdk?branch=release-virto-v1.5.0#b20a9cb40b2af12c56ea33c6d11104a162ea3d2e" +dependencies = [ + "enumflags2", + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std 8.0.0", +] + [[package]] name = "pallet-nis" version = "4.0.0-dev" @@ -6985,6 +7031,24 @@ dependencies = [ "sp-std 8.0.0", ] +[[package]] +name = "pallet-referenda-tracks" +version = "4.0.0-dev" +source = "git+https://github.com/virto-network/polkadot-sdk?branch=release-virto-v1.5.0#b20a9cb40b2af12c56ea33c6d11104a162ea3d2e" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "pallet-referenda", + "parity-scale-codec", + "scale-info", + "serde", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std 8.0.0", +] + [[package]] name = "pallet-root-testing" version = "1.0.0-dev" @@ -11952,7 +12016,7 @@ dependencies = [ [[package]] name = "sp-crypto-ec-utils" version = "0.10.0" -source = "git+https://github.com/paritytech/polkadot-sdk#ea458d0b95d819d31683a8a09ca7973ae10b49be" +source = "git+https://github.com/paritytech/polkadot-sdk#7a644fa082f09b557a8a198ed56412f02062bbcf" dependencies = [ "ark-bls12-377", "ark-bls12-377-ext", @@ -11992,7 +12056,7 @@ dependencies = [ [[package]] name = "sp-debug-derive" version = "14.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#ea458d0b95d819d31683a8a09ca7973ae10b49be" +source = "git+https://github.com/paritytech/polkadot-sdk#7a644fa082f09b557a8a198ed56412f02062bbcf" dependencies = [ "proc-macro2", "quote", @@ -12013,7 +12077,7 @@ dependencies = [ [[package]] name = "sp-externalities" version = "0.25.0" -source = "git+https://github.com/paritytech/polkadot-sdk#ea458d0b95d819d31683a8a09ca7973ae10b49be" +source = "git+https://github.com/paritytech/polkadot-sdk#7a644fa082f09b557a8a198ed56412f02062bbcf" dependencies = [ "environmental", "parity-scale-codec", @@ -12232,7 +12296,7 @@ dependencies = [ [[package]] name = "sp-runtime-interface" version = "24.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#ea458d0b95d819d31683a8a09ca7973ae10b49be" +source = "git+https://github.com/paritytech/polkadot-sdk#7a644fa082f09b557a8a198ed56412f02062bbcf" dependencies = [ "bytes", "impl-trait-for-tuples", @@ -12264,7 +12328,7 @@ dependencies = [ [[package]] name = "sp-runtime-interface-proc-macro" version = "17.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#ea458d0b95d819d31683a8a09ca7973ae10b49be" +source = "git+https://github.com/paritytech/polkadot-sdk#7a644fa082f09b557a8a198ed56412f02062bbcf" dependencies = [ "Inflector", "expander 2.1.0", @@ -12356,7 +12420,7 @@ source = "git+https://github.com/virto-network/polkadot-sdk?branch=release-virto [[package]] name = "sp-std" version = "14.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#ea458d0b95d819d31683a8a09ca7973ae10b49be" +source = "git+https://github.com/paritytech/polkadot-sdk#7a644fa082f09b557a8a198ed56412f02062bbcf" [[package]] name = "sp-storage" @@ -12374,7 +12438,7 @@ dependencies = [ [[package]] name = "sp-storage" version = "19.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#ea458d0b95d819d31683a8a09ca7973ae10b49be" +source = "git+https://github.com/paritytech/polkadot-sdk#7a644fa082f09b557a8a198ed56412f02062bbcf" dependencies = [ "impl-serde", "parity-scale-codec", @@ -12412,7 +12476,7 @@ dependencies = [ [[package]] name = "sp-tracing" version = "16.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#ea458d0b95d819d31683a8a09ca7973ae10b49be" +source = "git+https://github.com/paritytech/polkadot-sdk#7a644fa082f09b557a8a198ed56412f02062bbcf" dependencies = [ "parity-scale-codec", "sp-std 14.0.0", @@ -12514,7 +12578,7 @@ dependencies = [ [[package]] name = "sp-wasm-interface" version = "20.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#ea458d0b95d819d31683a8a09ca7973ae10b49be" +source = "git+https://github.com/paritytech/polkadot-sdk#7a644fa082f09b557a8a198ed56412f02062bbcf" dependencies = [ "anyhow", "impl-trait-for-tuples", @@ -13235,14 +13299,14 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a9aad4a3066010876e8dcf5a8a06e70a558751117a145c6ce2b82c2e2054290" +checksum = "af06656561d28735e9c1cd63dfd57132c8155426aa6af24f36a00a351f88c48e" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.6", + "toml_edit 0.22.7", ] [[package]] @@ -13291,9 +13355,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.6" +version = "0.22.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c1b5fd4128cc8d3e0cb74d4ed9a9cc7c7284becd4df68f5f940e1ad123606f6" +checksum = "18769cd1cec395d70860ceb4d932812a0b4d06b1a4bb336745a4d21b9496e992" dependencies = [ "indexmap 2.2.5", "serde", diff --git a/pallets/communities/Cargo.toml b/pallets/communities/Cargo.toml index 719da092..88954ded 100644 --- a/pallets/communities/Cargo.toml +++ b/pallets/communities/Cargo.toml @@ -1,23 +1,24 @@ [package] name = "pallet-communities" version = "0.1.0" -description = "This pallet enables the creation of communities that are soverign entities with diverse forms of governance. In simpler terms, it can be considered a DAO Factory." authors = ["Virto Team"] -edition = "2021" +description = "This pallet enables the creation of communities that are soverign entities with diverse forms of governance. In simpler terms, it can be considered a DAO Factory." license = "MIT-0" -publish = false -repository = "https://github.com/virto-network/virto-node/" +homepage = 'https://github.com/virto-network/virto-node' +repository = 'https://github.com/virto-network/virto-node' +edition = "2021" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] -parity-scale-codec = { workspace = true, features = ["derive"] } -scale-info = { workspace = true, features = ["derive"] } - frame-benchmarking = { workspace = true, optional = true } frame-support = { workspace = true } frame-system = { workspace = true } + +parity-scale-codec = { workspace = true, features = ["derive"] } +scale-info = { workspace = true, features = ["derive"] } + sp-runtime = { workspace = true } sp-std = { workspace = true } xcm = { workspace = true, optional = true } @@ -42,12 +43,16 @@ std = [ "frame-support/std", "frame-system/std", "pallet-assets/std", - "sp-core/std", "pallet-balances/std", "pallet-nfts/std", + "pallet-preimage/std", "pallet-referenda/std", + "pallet-referenda-tracks/std", + "pallet-scheduler/std", "parity-scale-codec/std", + "sp-core/std", "scale-info/std", + "sp-core/std", "sp-runtime/std", "sp-std/std", "xcm?/std", @@ -55,5 +60,11 @@ std = [ runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", "pallet-assets/runtime-benchmarks", + "pallet-balances/runtime-benchmarks", + "pallet-nfts/runtime-benchmarks", + "pallet-preimage/runtime-benchmarks", + "pallet-referenda/runtime-benchmarks", + "pallet-referenda-tracks/runtime-benchmarks", + "pallet-scheduler/runtime-benchmarks", ] try-runtime = ["frame-support/try-runtime"] diff --git a/pallets/communities/src/benchmarking.rs b/pallets/communities/src/benchmarking.rs index 4cb5a7d2..5396dcce 100644 --- a/pallets/communities/src/benchmarking.rs +++ b/pallets/communities/src/benchmarking.rs @@ -2,34 +2,414 @@ #![cfg(feature = "runtime-benchmarks")] use super::*; -#[allow(unused)] -use crate::Pallet as Template; +use self::{ + origin::DecisionMethod, + types::{AccountIdOf, CommunityIdOf, DecisionMethodFor, MembershipIdOf, PalletsOriginOf, PollIndexOf, Vote}, + Event, HoldReason, Pallet as Communities, +}; use frame_benchmarking::v2::*; -use frame_system::RawOrigin; +use frame_support::{ + traits::{ + fungible::{InspectFreeze, Mutate}, + membership::{Inspect, WithRank}, + OriginTrait, + }, + BoundedVec, +}; +use frame_system::{ + pallet_prelude::{BlockNumberFor, OriginFor, RuntimeCallFor}, + RawOrigin, +}; +use sp_runtime::{traits::StaticLookup, DispatchError}; +use sp_std::{vec, vec::Vec}; -#[benchmarks] +fn assert_has_event(generic_event: ::RuntimeEvent) { + frame_system::Pallet::::assert_has_event(generic_event.into()); +} + +fn setup_accounts() -> Result>, BenchmarkError> { + let size = T::BenchmarkHelper::community_desired_size(); + let accounts = (0..size).map(|i| frame_benchmarking::account("community_benchmarking", i, 0)); + + for who in accounts.clone() { + T::Balances::mint_into(&who, 10u32.into())?; + } + + Ok(accounts.collect()) +} + +fn community_params( + maybe_decision_method: Option>, +) -> ( + CommunityIdOf, + DecisionMethodFor, + T::RuntimeOrigin, + PalletsOriginOf, +) { + let community_id = T::BenchmarkHelper::community_id(); + + let decision_method = maybe_decision_method.unwrap_or(DecisionMethod::Rank); + let admin_origin: T::RuntimeOrigin = T::BenchmarkHelper::community_origin(decision_method.clone()); + let admin_origin_caller: PalletsOriginOf = admin_origin.clone().into_caller(); + + (community_id, decision_method, admin_origin, admin_origin_caller) +} + +/// Creates a community, setting a [DecisionMethod], returning +/// its ID as well as the caller origin, and origin caller. +fn create_community( + origin: OriginFor, + maybe_decision_method: Option>, +) -> Result<(CommunityIdOf, OriginFor), BenchmarkError> { + T::BenchmarkHelper::initialize_memberships_collection()?; + let (community_id, decision_method, admin_origin, admin_origin_caller) = + community_params::(maybe_decision_method); + + Pallet::::create(origin.clone(), admin_origin_caller, community_id)?; + Pallet::::set_decision_method(origin, community_id, decision_method)?; + + Ok((community_id, admin_origin)) +} + +/// Initializes the memberships of a community built for benchmarking +/// purposes. +/// +/// Then, returns a list of tuples, each one containing a member's +/// [AccountId] and their corresponding +fn setup_members( + origin: OriginFor, + community_id: CommunityIdOf, +) -> Result, MembershipIdOf)>, frame_benchmarking::BenchmarkError> { + let members_with_memberships = setup_accounts::()? + .into_iter() + .enumerate() + .map(|(i, account_id)| (account_id, T::BenchmarkHelper::membership_id(community_id, i as u32))); + + for (who, membership_id) in members_with_memberships.clone() { + T::BenchmarkHelper::issue_membership(community_id, membership_id.clone())?; + + let who = T::Lookup::unlookup(who.clone()); + Pallet::::add_member(origin.clone(), who.clone())?; + Pallet::::promote_member(origin.clone(), who, membership_id)?; + } + + Ok(members_with_memberships.collect()) +} + +fn prepare_track_and_prepare_poll( + track_origin: PalletsOriginOf, + submitter: AccountIdOf, +) -> Result, BenchmarkError> +where + RuntimeCallFor: From>, +{ + T::BenchmarkHelper::prepare_track(track_origin.clone())?; + + let new_member = T::Lookup::unlookup(frame_benchmarking::account("community_benchmarking", 0, 0)); + T::BenchmarkHelper::prepare_poll( + RawOrigin::Signed(submitter).into(), + track_origin.clone(), + crate::Call::::add_member { who: new_member }.into(), + ) +} + +#[benchmarks( + where + T: frame_system::Config + crate::Config, + RuntimeCallFor: From>, + BlockNumberFor: From +)] mod benchmarks { use super::*; #[benchmark] - fn do_something() { - let value = 100u32.into(); - let caller: T::AccountId = whitelisted_caller(); + fn create() { + // setup code + let (id, _, _, origin) = community_params::(None); + + #[extrinsic_call] + _(RawOrigin::Root, origin.clone(), id); + + // verification code + assert_has_event::(Event::CommunityCreated { id, origin }.into()); + } + + #[benchmark] + fn set_metadata(n: Linear<1, 64>, d: Linear<1, 256>, u: Linear<1, 256>) -> Result<(), BenchmarkError> { + // setup code + let (id, _, _, admin_origin) = community_params::(None); + Communities::::create(RawOrigin::Root.into(), admin_origin, id)?; + + let name = Some(BoundedVec::truncate_from(vec![0u8; n as usize])); + let description = Some(BoundedVec::truncate_from(vec![0u8; d as usize])); + let url = Some(BoundedVec::truncate_from(vec![0u8; u as usize])); + + #[extrinsic_call] + _(RawOrigin::Root, id, name.clone(), description.clone(), url.clone()); + + // verification code + assert_has_event::( + Event::MetadataSet { + id, + name, + description, + main_url: url, + } + .into(), + ); + + Ok(()) + } + + #[benchmark] + fn set_decision_method() -> Result<(), BenchmarkError> { + // setup code + let (id, _, _, admin_origin) = community_params::(Default::default()); + Communities::::create(RawOrigin::Root.into(), admin_origin, id)?; + + #[extrinsic_call] + _(RawOrigin::Root, id, DecisionMethod::Membership); + + // verification code + assert_has_event::(Event::DecisionMethodSet { id }.into()); + + Ok(()) + } + + #[benchmark] + fn add_member() -> Result<(), BenchmarkError> { + // setup code + let (id, origin) = create_community::(RawOrigin::Root.into(), None)?; + + let who: AccountIdOf = frame_benchmarking::account("community_benchmarking", 0, 0); + let membership_id = T::BenchmarkHelper::membership_id(id, 0); + + T::BenchmarkHelper::issue_membership(id, membership_id.clone())?; + + #[extrinsic_call] + _(origin.into_caller(), T::Lookup::unlookup(who.clone())); + + // verification code + assert_has_event::( + Event::MemberAdded { + who: who.clone(), + membership_id: membership_id.clone(), + } + .into(), + ); + assert!(Communities::::has_membership(&who, membership_id)); + + Ok(()) + } + + #[benchmark] + fn remove_member() -> Result<(), BenchmarkError> { + // setup code + let (id, origin): (CommunityIdOf, OriginFor) = create_community::(RawOrigin::Root.into(), None)?; + let community_account_id = Communities::::community_account(&id); + + let who: AccountIdOf = frame_benchmarking::account("community_benchmarking", 0, 0); + let membership_id = T::BenchmarkHelper::membership_id(id, 0); + + T::BenchmarkHelper::issue_membership(id, membership_id.clone())?; + + Communities::::add_member(origin.clone(), T::Lookup::unlookup(who.clone()))?; + + #[extrinsic_call] + _( + RawOrigin::Signed(community_account_id), + T::Lookup::unlookup(who.clone()), + membership_id.clone(), + ); + + // verification code + assert_has_event::( + Event::MemberRemoved { + who: who.clone(), + membership_id: membership_id.clone(), + } + .into(), + ); + assert!(!Communities::::has_membership(&who, membership_id)); + + Ok(()) + } + + #[benchmark] + fn promote_member() -> Result<(), BenchmarkError> { + // setup code + let (id, origin): (CommunityIdOf, OriginFor) = create_community::(RawOrigin::Root.into(), None)?; + + let who: AccountIdOf = frame_benchmarking::account("community_benchmarking", 0, 0); + let membership_id = T::BenchmarkHelper::membership_id(id, 0); + + T::BenchmarkHelper::issue_membership(id, membership_id.clone())?; + + Communities::::add_member(origin.clone(), T::Lookup::unlookup(who.clone()))?; + + #[extrinsic_call] + _( + origin.into_caller(), + T::Lookup::unlookup(who.clone()), + membership_id.clone(), + ); + + // verification code + let m = T::MemberMgmt::get_membership(membership_id.clone(), &who) + .ok_or::(Error::::NotAMember.into())?; + let rank = m.rank(); + + assert_has_event::( + Event::MembershipRankUpdated { + membership_id: membership_id.clone(), + rank, + } + .into(), + ); + + assert_eq!(Communities::::member_rank(&who, membership_id), Some(rank)); + + Ok(()) + } + + #[benchmark] + fn demote_member() -> Result<(), BenchmarkError> { + // setup code + let (id, origin): (CommunityIdOf, OriginFor) = create_community::(RawOrigin::Root.into(), None)?; + + let who: AccountIdOf = frame_benchmarking::account("community_benchmarking", 0, 0); + let membership_id = T::BenchmarkHelper::membership_id(id, 0); + + T::BenchmarkHelper::issue_membership(id, membership_id.clone())?; + + Communities::::add_member(origin.clone(), T::Lookup::unlookup(who.clone()))?; + + Communities::::promote_member(origin.clone(), T::Lookup::unlookup(who.clone()), membership_id.clone())?; + #[extrinsic_call] - do_something(RawOrigin::Signed(caller), value); + _( + origin.into_caller(), + T::Lookup::unlookup(who.clone()), + membership_id.clone(), + ); - assert_eq!(Something::::get(), Some(value)); + // verification code + assert_eq!(Communities::::member_rank(&who, membership_id), Some(0.into())); + + Ok(()) + } + + #[benchmark] + fn vote() -> Result<(), BenchmarkError> { + // setup code + let (id, origin) = create_community::(RawOrigin::Root.into(), None)?; + let members = setup_members::(origin.clone(), id)?; + + let (who, membership_id) = members + .first() + .expect("desired size of community to be equal or greather than 1") + .clone(); + + prepare_track_and_prepare_poll::(origin.into_caller(), who.clone())?; + + #[extrinsic_call] + _( + RawOrigin::Signed(who.clone()), + membership_id.clone(), + 0u32, + Vote::Standard(true), + ); + + // verification code + assert_has_event::( + Event::VoteCasted { + who: who.clone(), + poll_index: 0u32.into(), + vote: Vote::Standard(true), + } + .into(), + ); + + Ok(()) } #[benchmark] - fn cause_error() { - Something::::put(100u32); - let caller: T::AccountId = whitelisted_caller(); + fn remove_vote() -> Result<(), BenchmarkError> { + // setup code + let (id, origin) = create_community::(RawOrigin::Root.into(), None)?; + let members = setup_members::(origin.clone(), id)?; + + let (who, membership_id) = members + .first() + .expect("desired size of community to be equal or greather than 1") + .clone(); + + prepare_track_and_prepare_poll::(origin.into_caller(), who.clone())?; + + Communities::::vote( + RawOrigin::Signed(who.clone()).into(), + membership_id.clone(), + 0u32, + Vote::Standard(true), + )?; + #[extrinsic_call] - cause_error(RawOrigin::Signed(caller)); + _(RawOrigin::Signed(who.clone()), membership_id.clone(), 0u32); + + // verification code + assert_has_event::( + Event::VoteRemoved { + who: who.clone(), + poll_index: 0u32.into(), + } + .into(), + ); + + Ok(()) + } + + #[benchmark] + fn unlock() -> Result<(), BenchmarkError> { + // setup code + let (id, origin) = create_community::(RawOrigin::Root.into(), Some(DecisionMethod::NativeToken))?; + let members = setup_members::(origin.clone(), id)?; + + let (who, membership_id) = members + .first() + .expect("desired size of community to be equal or greather than 1") + .clone(); + + let index = prepare_track_and_prepare_poll::(origin.into_caller(), who.clone())?; + + Communities::::vote( + RawOrigin::Signed(who.clone()).into(), + membership_id.clone(), + 0u32, + Vote::NativeBalance(true, 1u32.into()), + )?; + + assert_eq!( + T::Balances::balance_frozen(&HoldReason::VoteCasted(0u32).into(), &who), + 1u32.into() + ); + + T::BenchmarkHelper::finish_poll(index)?; + + #[extrinsic_call] + _(RawOrigin::Signed(who.clone()), 0u32); + + // verification code + assert_eq!( + T::Balances::balance_frozen(&HoldReason::VoteCasted(0u32).into(), &who), + 0u32.into() + ); - assert_eq!(Something::::get(), Some(101u32)); + Ok(()) } - impl_benchmark_test_suite!(Template, crate::mock::new_test_ext(), crate::mock::Test); + impl_benchmark_test_suite!( + Communities, + sp_io::TestExternalities::new(Default::default()), + crate::mock::Test + ); } diff --git a/pallets/communities/src/functions.rs b/pallets/communities/src/functions.rs index 9962c6b9..989f92b2 100644 --- a/pallets/communities/src/functions.rs +++ b/pallets/communities/src/functions.rs @@ -169,7 +169,7 @@ impl Pallet { let community_id = CommunityIdOf::::from(membership_id.clone()); T::Polls::try_access_poll(poll_index, |poll_status| { - if let Some((tally, class)) = poll_status.ensure_ongoing() { + let res = if let Some((tally, class)) = poll_status.ensure_ongoing() { ensure!(community_id == class, Error::::InvalidTrack); let vote = Self::community_vote_of(who, poll_index).ok_or(Error::::NoVoteCasted)?; @@ -195,7 +195,14 @@ impl Pallet { } } else { Err(Error::::NotOngoing.into()) - } + }; + + Self::deposit_event(Event::::VoteRemoved { + who: who.clone(), + poll_index, + }); + + res }) } diff --git a/pallets/communities/src/impls.rs b/pallets/communities/src/impls.rs index 244b9a09..eea66f15 100644 --- a/pallets/communities/src/impls.rs +++ b/pallets/communities/src/impls.rs @@ -22,4 +22,41 @@ impl VoteTally> for Tally { fn approval(&self, _cid: CommunityIdOf) -> sp_runtime::Perbill { Perbill::from_rational(self.ayes, 1.max(self.ayes + self.nays)) } + + #[cfg(feature = "runtime-benchmarks")] + fn unanimity(community_id: CommunityIdOf) -> Self { + Self { + ayes: Self::max_support(community_id), + bare_ayes: Self::max_support(community_id), + nays: 0, + ..Default::default() + } + } + + #[cfg(feature = "runtime-benchmarks")] + fn rejection(community_id: CommunityIdOf) -> Self { + Self { + ayes: 0, + bare_ayes: 0, + nays: Self::max_support(community_id), + ..Default::default() + } + } + + #[cfg(feature = "runtime-benchmarks")] + fn from_requirements(support: Perbill, approval: Perbill, community_id: CommunityIdOf) -> Self { + let approval_weight = approval * Self::max_support(community_id); + let rejection_weight = (Perbill::from_percent(100) - approval) * Self::max_support(community_id); + let support_weight = support * Self::max_support(community_id); + + Self { + ayes: approval_weight, + nays: rejection_weight, + bare_ayes: support_weight, + ..Default::default() + } + } + + #[cfg(feature = "runtime-benchmarks")] + fn setup(_community_id: CommunityIdOf, _granularity: Perbill) {} } diff --git a/pallets/communities/src/lib.rs b/pallets/communities/src/lib.rs index 922e536e..7682cdda 100644 --- a/pallets/communities/src/lib.rs +++ b/pallets/communities/src/lib.rs @@ -166,6 +166,11 @@ pub use pallet::*; #[cfg(feature = "runtime-benchmarks")] mod benchmarking; +#[cfg(feature = "runtime-benchmarks")] +pub use types::BenchmarkHelper; + +#[cfg(test)] +mod mock; #[cfg(test)] mod tests; @@ -211,10 +216,10 @@ pub mod pallet { + membership::Mutate; /// Origin authorized to manage the state of a community - type CommunityMgmtOrigin: EnsureOrigin; + type CommunityMgmtOrigin: EnsureOrigin>; /// Origin authorized to manage memeberships of an active community - type MemberMgmtOrigin: EnsureOrigin; + type MemberMgmtOrigin: EnsureOrigin, Success = Self::CommunityId>; type Polls: Polling< Tally, @@ -231,6 +236,7 @@ pub mod pallet { /// Type represents interactions between fungible tokens (native token) type Balances: fungible::Inspect + fungible::Mutate + + fungible::freeze::Inspect + fungible::freeze::Mutate; /// The overarching hold reason. @@ -246,6 +252,9 @@ pub mod pallet { /// The pallet id used for deriving sovereign account IDs. #[pallet::constant] type PalletId: Get; + + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper: BenchmarkHelper; } /// The origin of the pallet @@ -333,6 +342,10 @@ pub mod pallet { poll_index: PollIndexOf, vote: VoteOf, }, + VoteRemoved { + who: AccountIdOf, + poll_index: PollIndexOf, + }, } // Errors inform users that something worked or went wrong. @@ -393,6 +406,11 @@ pub mod pallet { /// /// [11]: `types::CommunityMetadata` #[pallet::call_index(1)] + #[pallet::weight(::WeightInfo::set_metadata( + name.as_ref().map(|x| x.len() as u32).unwrap_or(0), + description.as_ref().map(|x| x.len() as u32).unwrap_or(0), + url.as_ref().map(|x| x.len() as u32).unwrap_or(0), + ))] pub fn set_metadata( origin: OriginFor, community_id: T::CommunityId, diff --git a/pallets/communities/src/tests/mock.rs b/pallets/communities/src/mock.rs similarity index 70% rename from pallets/communities/src/tests/mock.rs rename to pallets/communities/src/mock.rs index f391c1e0..3d1bb873 100644 --- a/pallets/communities/src/tests/mock.rs +++ b/pallets/communities/src/mock.rs @@ -21,7 +21,7 @@ pub use virto_common::{CommunityId, MembershipId, MembershipInfo}; use crate::{ self as pallet_communities, - origin::{DecisionMethod, EnsureCommunity}, + origin::{DecisionMethod, EnsureCommunity, EnsureCommunityAccountId}, types::{Tally, VoteWeight}, }; @@ -82,7 +82,7 @@ impl pallet_assets::Config for Test { type RuntimeEvent = RuntimeEvent; type Balance = Balance; type AssetId = AssetId; - type AssetIdParameter = Compact; + type AssetIdParameter = Compact; type Currency = Balances; type CreateOrigin = AsEnsureOriginWithArg>; type ForceOrigin = EnsureRoot; @@ -121,6 +121,17 @@ impl pallet_balances::Config for Test { } // Memberships +#[cfg(feature = "runtime-benchmarks")] +pub struct NftsBenchmarksHelper; +#[cfg(feature = "runtime-benchmarks")] +impl pallet_nfts::BenchmarkHelper for NftsBenchmarksHelper { + fn collection(i: u16) -> CollectionId { + i.into() + } + fn item(i: u16) -> MembershipId { + MembershipId(COMMUNITY, i.into()) + } +} parameter_types! { pub const RootAccount: AccountId = AccountId::new([0xff; 32]); @@ -151,6 +162,9 @@ impl pallet_nfts::Config for Test { type StringLimit = (); type ValueLimit = ConstU32<10>; type WeightInfo = (); + + #[cfg(feature = "runtime-benchmarks")] + type Helper = NftsBenchmarksHelper; } // Governance at Communities @@ -201,6 +215,24 @@ impl EnsureOriginWithArg> for EnsureOriginToT Ok(()) } + + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin(id: &TrackIdOf) -> Result { + Ok(pallet_communities::Origin::::new(id.clone()).into()) + } +} + +#[cfg(feature = "runtime-benchmarks")] +use sp_runtime::SaturatedConversion; + +#[cfg(feature = "runtime-benchmarks")] +pub struct TracksBenchmarkHelper; + +#[cfg(feature = "runtime-benchmarks")] +impl pallet_referenda_tracks::BenchmarkHelper for TracksBenchmarkHelper { + fn track_id(id: u32) -> TrackIdOf { + CommunityId::new(id.saturated_into()) + } } parameter_types! { @@ -213,6 +245,9 @@ impl pallet_referenda_tracks::Config for Test { type AdminOrigin = EnsureRoot; type UpdateOrigin = EnsureOriginToTrack; type WeightInfo = (); + + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = TracksBenchmarkHelper; } parameter_types! { @@ -250,12 +285,130 @@ parameter_types! { type MembershipCollection = ItemOf; pub type Memberships = NonFungibleAdpter; +#[cfg(feature = "runtime-benchmarks")] +use crate::{ + types::{CommunityIdOf, DecisionMethodFor, MembershipIdOf, PollIndexOf}, + BenchmarkHelper, Origin, +}; + +#[cfg(feature = "runtime-benchmarks")] +use { + frame_benchmarking::BenchmarkError, + frame_support::BoundedVec, + frame_system::pallet_prelude::{OriginFor, RuntimeCallFor}, + pallet_referenda::{BoundedCallOf, Curve, PalletsOriginOf, TrackInfo}, + parity_scale_codec::Encode, + sp_runtime::Perbill, +}; + +#[cfg(feature = "runtime-benchmarks")] +pub struct CommunityBenchmarkHelper; + +#[cfg(feature = "runtime-benchmarks")] +impl BenchmarkHelper for CommunityBenchmarkHelper { + fn community_id() -> CommunityIdOf { + COMMUNITY + } + + fn community_desired_size() -> u32 { + u8::MAX as u32 + } + + fn community_origin(decision_method: DecisionMethodFor) -> OriginFor { + let mut origin = Origin::::new(Self::community_id()); + origin.with_decision_method(decision_method); + + origin.into() + } + + fn membership_id(community_id: CommunityIdOf, index: u32) -> MembershipIdOf { + MembershipId(community_id, index) + } + + fn initialize_memberships_collection() -> Result<(), frame_benchmarking::BenchmarkError> { + TestEnvBuilder::initialize_memberships_collection(); + Ok(()) + } + + fn issue_membership( + community_id: CommunityIdOf, + membership_id: MembershipIdOf, + ) -> Result<(), frame_benchmarking::BenchmarkError> { + use frame_support::traits::tokens::nonfungible_v2::Mutate; + + let community_account = Communities::community_account(&community_id); + MembershipCollection::mint_into(&membership_id, &community_account, &Default::default(), true)?; + + Ok(()) + } + + fn prepare_track(track_origin: PalletsOriginOf) -> Result<(), BenchmarkError> { + let id = Self::community_id(); + let info = TrackInfo { + name: sp_runtime::str_array("Community"), + max_deciding: 1, + decision_deposit: 5, + prepare_period: 1, + decision_period: 5, + confirm_period: 1, + min_enactment_period: 1, + min_approval: Curve::LinearDecreasing { + length: Perbill::from_percent(100), + floor: Perbill::from_percent(50), + ceil: Perbill::from_percent(100), + }, + min_support: Curve::LinearDecreasing { + length: Perbill::from_percent(100), + floor: Perbill::from_percent(0), + ceil: Perbill::from_percent(100), + }, + }; + + Tracks::insert(RuntimeOrigin::root(), id, info, track_origin.clone())?; + + Ok(()) + } + + fn prepare_poll( + origin: OriginFor, + proposal_origin: PalletsOriginOf, + proposal_call: RuntimeCallFor, + ) -> Result, BenchmarkError> { + let proposal = BoundedCallOf::::Inline(BoundedVec::truncate_from(proposal_call.encode())); + let enactment_moment = frame_support::traits::schedule::DispatchTime::After(1); + Referenda::submit(origin.clone(), Box::new(proposal_origin), proposal, enactment_moment)?; + Referenda::place_decision_deposit(origin, 0)?; + + System::set_block_number(2); + Referenda::nudge_referendum(RuntimeOrigin::root(), 0)?; + + Ok(0) + } + + fn finish_poll(index: PollIndexOf) -> Result<(), BenchmarkError> { + System::set_block_number(8); + Referenda::nudge_referendum(RuntimeOrigin::root(), index)?; + + frame_support::assert_ok!(Referenda::ensure_ongoing(index)); + + System::set_block_number(9); + Referenda::nudge_referendum(RuntimeOrigin::root(), index)?; + + frame_support::assert_err!( + Referenda::ensure_ongoing(index), + pallet_referenda::Error::::NotOngoing + ); + + Ok(()) + } +} + impl pallet_communities::Config for Test { type Assets = Assets; type Balances = Balances; type CommunityId = CommunityId; type CommunityMgmtOrigin = EnsureRoot; - type MemberMgmtOrigin = EnsureCommunity; + type MemberMgmtOrigin = EitherOf, EnsureCommunityAccountId>; type MemberMgmt = Memberships; type Membership = MembershipInfo; type PalletId = CommunitiesPalletId; @@ -263,6 +416,9 @@ impl pallet_communities::Config for Test { type RuntimeEvent = RuntimeEvent; type RuntimeHoldReason = RuntimeHoldReason; type WeightInfo = WeightInfo; + + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = CommunityBenchmarkHelper; } pub const COMMUNITY: CommunityId = CommunityId::new(1); @@ -367,19 +523,7 @@ impl TestEnvBuilder { ext.execute_with(|| { System::set_block_number(1); - let collection = MembershipsCollectionId::get(); - Nfts::do_create_collection( - collection, - RootAccount::get(), - RootAccount::get(), - Default::default(), - 0, - pallet_nfts::Event::ForceCreated { - collection, - owner: RootAccount::get(), - }, - ) - .expect("creates memberships collection"); + Self::initialize_memberships_collection(); for community_id in &self.communities { let decision_method = self @@ -429,12 +573,28 @@ impl TestEnvBuilder { ext } + pub(crate) fn initialize_memberships_collection() { + let collection = MembershipsCollectionId::get(); + Nfts::do_create_collection( + collection, + RootAccount::get(), + RootAccount::get(), + Default::default(), + 0, + pallet_nfts::Event::ForceCreated { + collection, + owner: RootAccount::get(), + }, + ) + .expect("creates memberships collection"); + } + pub fn create_community_origin( community_id: &CommunityId, decision_method: &DecisionMethod, ) -> RuntimeOrigin { let mut origin = pallet_communities::Origin::::new(*community_id); - origin.set_decision_method(decision_method.clone()); + origin.with_decision_method(decision_method.clone()); origin.into() } diff --git a/pallets/communities/src/origin.rs b/pallets/communities/src/origin.rs index 5c029267..6eb19a0a 100644 --- a/pallets/communities/src/origin.rs +++ b/pallets/communities/src/origin.rs @@ -1,13 +1,14 @@ use crate::{ types::{AssetIdOf, CommunityIdOf, CommunityState::Active, MembershipIdOf}, - CommunityIdFor, Config, Info, + CommunityIdFor, Config, Info, Pallet, }; use core::marker::PhantomData; use frame_support::{ pallet_prelude::*, traits::{membership::GenericRank, OriginTrait}, + PalletId, }; -use sp_runtime::Permill; +use sp_runtime::{traits::AccountIdConversion, Permill}; pub struct EnsureCommunity(PhantomData); @@ -34,6 +35,38 @@ where .and_then(|c| c.state.eq(&Active).then_some(id)) .ok_or(o) } + + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin() -> Result { + use crate::BenchmarkHelper; + Ok(RawOrigin::new(T::BenchmarkHelper::community_id()).into()) + } +} + +pub struct EnsureCommunityAccountId(PhantomData); + +impl EnsureOrigin for EnsureCommunityAccountId +where + T::RuntimeOrigin: OriginTrait + From> + From>, + T: Config, +{ + type Success = T::CommunityId; + + fn try_origin(o: T::RuntimeOrigin) -> Result { + match o.clone().into() { + Ok(frame_system::RawOrigin::Signed(account_id)) => { + let (_, community_id) = PalletId::try_from_sub_account(&account_id).ok_or(o.clone())?; + Ok(community_id) + } + _ => Err(o), + } + } + + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin() -> Result { + use crate::BenchmarkHelper; + Ok(RawOrigin::new(T::BenchmarkHelper::community_id()).into()) + } } /// Origin to represent the voice of a community or a subset of its members @@ -58,6 +91,10 @@ impl RawOrigin { self.subset = Some(s); } + pub fn with_decision_method(&mut self, m: DecisionMethod>) { + self.method = m; + } + pub fn id(&self) -> CommunityIdOf { self.community_id } @@ -65,11 +102,6 @@ impl RawOrigin { pub fn decision_method(&self) -> DecisionMethod> { self.method.clone() } - - #[cfg(test)] - pub(crate) fn set_decision_method(&mut self, decision_method: DecisionMethod>) { - self.method = decision_method; - } } /// Subsets of the community can also have a voice @@ -144,3 +176,32 @@ where Ok(origin) } } + +/// Ensure the origin is any `Signed` origin. +pub struct AsSignedByCommunity(PhantomData); +impl EnsureOrigin for AsSignedByCommunity +where + OuterOrigin: OriginTrait + + From> + + From> + + Clone + + Into, OuterOrigin>> + + Into, OuterOrigin>>, + T: Config, +{ + type Success = T::AccountId; + + fn try_origin(o: OuterOrigin) -> Result { + match o.clone().into() { + Ok(RawOrigin { community_id, .. }) => Ok(Pallet::::community_account(&community_id)), + _ => Err(o.clone()), + } + } + + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin() -> Result { + use crate::BenchmarkHelper; + let community_id = T::BenchmarkHelper::community_id(); + Ok(frame_system::RawOrigin::Signed(Pallet::::community_account(&community_id)).into()) + } +} diff --git a/pallets/communities/src/tests/governance.rs b/pallets/communities/src/tests/governance.rs index 58e0a331..a13896f1 100644 --- a/pallets/communities/src/tests/governance.rs +++ b/pallets/communities/src/tests/governance.rs @@ -1014,6 +1014,14 @@ mod remove_vote { 0 )); + System::assert_has_event( + crate::Event::::VoteRemoved { + who: ALICE, + poll_index: 0, + } + .into(), + ); + assert_eq!( Referenda::as_ongoing(0).expect("we already created poll 0; qed").0, Tally::default() @@ -1045,6 +1053,14 @@ mod remove_vote { 2 )); + System::assert_has_event( + crate::Event::::VoteRemoved { + who: ALICE, + poll_index: 2, + } + .into(), + ); + assert_eq!( Referenda::as_ongoing(2).expect("we already created poll 2; qed").0, Tally::default() diff --git a/pallets/communities/src/tests/mod.rs b/pallets/communities/src/tests/mod.rs index 872f991d..0b6f6651 100644 --- a/pallets/communities/src/tests/mod.rs +++ b/pallets/communities/src/tests/mod.rs @@ -1,10 +1,9 @@ use frame_support::assert_ok; mod helpers; -mod mock; +use crate::mock::*; use helpers::*; -use mock::*; type Error = crate::Error; diff --git a/pallets/communities/src/types.rs b/pallets/communities/src/types.rs index 61889373..7b4160ab 100644 --- a/pallets/communities/src/types.rs +++ b/pallets/communities/src/types.rs @@ -25,6 +25,9 @@ pub type MembershipIdOf = <::Membership as membership::Membershi pub type SizedField = BoundedVec; pub type ConstSizedField = SizedField>; +#[cfg(feature = "runtime-benchmarks")] +pub type BenchmarkHelperOf = ::BenchmarkHelper; + /// The Community struct holds the basic definition of a community. It includes /// the current state of a community, the [`AccountId`][1] for the community /// admin, and (if any) the ID of the community-issued asset the community has @@ -134,3 +137,50 @@ impl Tally { } } } + +#[cfg(feature = "runtime-benchmarks")] +use { + frame_benchmarking::BenchmarkError, + frame_system::pallet_prelude::{OriginFor, RuntimeCallFor}, +}; + +#[cfg(feature = "runtime-benchmarks")] +pub trait BenchmarkHelper { + /// Returns the ID of the community to use in benchmarks + fn community_id() -> CommunityIdOf; + + /// Returns the desired size of the community for + /// effects of benchmark testing + fn community_desired_size() -> u32; + + /// Returns the origin for the community + /// as well as the caller + fn community_origin(decision_method: DecisionMethodFor) -> OriginFor; + + /// Returns a new membership ID for a community with a given index. + fn membership_id(community_id: CommunityIdOf, index: u32) -> MembershipIdOf; + + /// Initializes the membership collection of a community. + fn initialize_memberships_collection() -> Result<(), frame_benchmarking::BenchmarkError>; + + /// Extends the membership collection of a community with a given + /// membership ID. + fn issue_membership( + community_id: CommunityIdOf, + membership_id: MembershipIdOf, + ) -> Result<(), frame_benchmarking::BenchmarkError>; + + /// This method prepares the referenda track to be used + /// to submit the poll, for benchmarking purposes. + fn prepare_track(track_origin: PalletsOriginOf) -> Result<(), BenchmarkError>; + + /// This method prepares the poll to be used to + /// benchmark vote-related calls. + fn prepare_poll( + origin: OriginFor, + proposal_origin: PalletsOriginOf, + proposal_call: RuntimeCallFor, + ) -> Result, BenchmarkError>; + + fn finish_poll(index: PollIndexOf) -> Result<(), BenchmarkError>; +} diff --git a/pallets/communities/src/weights.rs b/pallets/communities/src/weights.rs index 076fd9e7..9a67f9af 100644 --- a/pallets/communities/src/weights.rs +++ b/pallets/communities/src/weights.rs @@ -35,7 +35,7 @@ use core::marker::PhantomData; /// Weight functions needed for pallet_communities. pub trait WeightInfo { fn create() -> Weight; - fn set_metadata() -> Weight; + fn set_metadata(n: u32, d: u32, u: u32) -> Weight; fn add_member() -> Weight; fn promote_member() -> Weight; fn demote_member() -> Weight; @@ -62,7 +62,7 @@ impl WeightInfo for SubstrateWeight { /// Storage: Communities Something (r:0 w:1) /// Proof: Communities Something (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - fn set_metadata() -> Weight { + fn set_metadata(_: u32, _: u32, _: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` @@ -175,7 +175,7 @@ impl WeightInfo for () { /// Storage: Communities Something (r:0 w:1) /// Proof: Communities Something (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - fn set_metadata() -> Weight { + fn set_metadata(_: u32, _: u32, _: u32) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` diff --git a/runtime/kreivo/Cargo.toml b/runtime/kreivo/Cargo.toml index e3fc6e1b..9eaa421b 100644 --- a/runtime/kreivo/Cargo.toml +++ b/runtime/kreivo/Cargo.toml @@ -27,8 +27,9 @@ pallet-burner = { workspace = true } pallet-payments = { workspace = true } pallet-communities = { workspace = true } -virto-common = { workspace = true } +# Local: Common runtime-common = { workspace = true } +virto-common = { workspace = true } # Substrate frame-benchmarking = { workspace = true, optional = true } @@ -44,10 +45,13 @@ pallet-assets = { workspace = true } pallet-asset-tx-payment = { workspace = true } pallet-authorship = { workspace = true } pallet-balances = { workspace = true } +pallet-nfts = { workspace = true } pallet-multisig = { workspace = true } pallet-utility = { workspace = true } pallet-preimage = { workspace = true } pallet-proxy = { workspace = true } +pallet-referenda = { workspace = true } +pallet-referenda-tracks = { workspace = true } pallet-scheduler = { workspace = true } pallet-session = { workspace = true } pallet-sudo = { workspace = true } @@ -118,11 +122,15 @@ std = [ "pallet-authorship/std", "pallet-balances/std", "pallet-burner/std", + "pallet-communities/std", "pallet-collator-selection/std", "pallet-message-queue/std", "pallet-multisig/std", + "pallet-nfts/std", "pallet-preimage/std", "pallet-proxy/std", + "pallet-referenda/std", + "pallet-referenda-tracks/std", "pallet-scheduler/std", "pallet-session/std", "pallet-sudo/std", @@ -170,11 +178,15 @@ runtime-benchmarks = [ "pallet-assets/runtime-benchmarks", "pallet-balances/runtime-benchmarks", "pallet-burner/runtime-benchmarks", + "pallet-communities/runtime-benchmarks", "pallet-collator-selection/runtime-benchmarks", "pallet-multisig/runtime-benchmarks", + "pallet-nfts/runtime-benchmarks", "pallet-payments/runtime-benchmarks", "pallet-preimage/runtime-benchmarks", "pallet-proxy/runtime-benchmarks", + "pallet-referenda/runtime-benchmarks", + "pallet-referenda-tracks/runtime-benchmarks", "pallet-scheduler/runtime-benchmarks", "pallet-timestamp/runtime-benchmarks", "pallet-treasury/runtime-benchmarks", diff --git a/runtime/kreivo/src/communities/governance.rs b/runtime/kreivo/src/communities/governance.rs new file mode 100644 index 00000000..5a45ff51 --- /dev/null +++ b/runtime/kreivo/src/communities/governance.rs @@ -0,0 +1,125 @@ +use super::*; + +use frame_system::{pallet_prelude::BlockNumberFor, EnsureRootWithSuccess}; +use sp_std::marker::PhantomData; + +use pallet_communities::types::CommunityIdOf; +use pallet_referenda::{BalanceOf, PalletsOriginOf, TrackIdOf, TracksInfo}; +use parachains_common::kusama::currency::QUID; + +pub type CommunityTracksInstance = pallet_referenda_tracks::Instance2; +pub type CommunityReferendaInstance = pallet_referenda::Instance2; + +parameter_types! { + pub const AlarmInterval: BlockNumber = 1; + pub const SubmissionDeposit: Balance = QUID; + pub const UndecidingTimeout: BlockNumber = 14 * DAYS; +} + +pub struct EnsureOriginToTrack; +impl EnsureOriginWithArg> for EnsureOriginToTrack { + type Success = (); + + fn try_origin( + o: RuntimeOrigin, + id: &TrackIdOf, + ) -> Result { + let track_id_for_origin: TrackIdOf = + CommunityTracks::track_for(&o.clone().caller).map_err(|_| o.clone())?; + ensure!(&track_id_for_origin == id, o); + + Ok(()) + } + + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin(id: &TrackIdOf) -> Result { + Ok(pallet_communities::Origin::::new(id.clone()).into()) + } +} + +impl pallet_referenda_tracks::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type TrackId = CommunityId; + type MaxTracks = ConstU32<65536>; + type AdminOrigin = EnsureRoot; + type UpdateOrigin = EnsureOriginToTrack; + type WeightInfo = pallet_referenda_tracks::weights::SubstrateWeight; + + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = CommunityTracksBenchmarkHelper; +} + +pub struct EnsureCommunityMember(PhantomData, PhantomData); + +impl EnsureOriginWithArg> for EnsureCommunityMember +where + T: pallet_communities::Config + pallet_referenda::Config, + T::Tracks: TracksInfo< + BalanceOf, + BlockNumberFor, + RuntimeOrigin = PalletsOriginOf, + Id = ::CommunityId, + >, +{ + type Success = T::AccountId; + + fn try_origin(o: T::RuntimeOrigin, track_origin: &PalletsOriginOf) -> Result { + match o.clone().into() { + Ok(frame_system::RawOrigin::Signed(account_id)) => { + let community_id = T::Tracks::track_for(track_origin).map_err(|_| o.clone())?; + + use frame_support::traits::membership::Inspect; + if T::MemberMgmt::account_memberships(&account_id) + .any(|membership_id| CommunityIdOf::::from(membership_id.clone()) == community_id) + { + Ok(account_id) + } else { + Err(o) + } + } + _ => Err(o), + } + } + + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin(_track_origin: &PalletsOriginOf) -> Result { + todo!() + } +} + +// Paritally from https://github.com/polkadot-fellows/runtimes/blob/b5ba0e91d5dd3c4020e848b27be5f2b47e16f281/relay/kusama/src/governance/mod.rs#L75 +impl pallet_referenda::Config for Runtime { + type WeightInfo = pallet_referenda::weights::SubstrateWeight; + type RuntimeCall = RuntimeCall; + type RuntimeEvent = RuntimeEvent; + type Scheduler = Scheduler; + type Currency = Balances; + type SubmitOrigin = EitherOf< + EnsureRootWithSuccess, + EnsureCommunityMember, + >; + type CancelOrigin = EnsureRoot; + type KillOrigin = EnsureRoot; + type Slash = Treasury; + type Votes = pallet_communities::types::VoteWeight; + type Tally = pallet_communities::types::Tally; + type SubmissionDeposit = SubmissionDeposit; + type MaxQueued = ConstU32<100>; + type UndecidingTimeout = UndecidingTimeout; + type AlarmInterval = AlarmInterval; + type Tracks = CommunityTracks; + type Preimages = Preimage; +} + +#[cfg(feature = "runtime-benchmarks")] +use sp_runtime::SaturatedConversion; + +#[cfg(feature = "runtime-benchmarks")] +pub struct CommunityTracksBenchmarkHelper; + +#[cfg(feature = "runtime-benchmarks")] +impl pallet_referenda_tracks::BenchmarkHelper for CommunityTracksBenchmarkHelper { + fn track_id(id: u32) -> TrackIdOf { + CommunityId::new(id.saturated_into()) + } +} diff --git a/runtime/kreivo/src/communities/memberships.rs b/runtime/kreivo/src/communities/memberships.rs new file mode 100644 index 00000000..f90b9051 --- /dev/null +++ b/runtime/kreivo/src/communities/memberships.rs @@ -0,0 +1,61 @@ +use super::*; + +use frame_system::EnsureRootWithSuccess; +use virto_common::MembershipId; + +pub type CommunityMembershipsInstance = pallet_nfts::Instance2; + +pub type CollectionId = u16; + +// From https://github.com/polkadot-fellows/runtimes/blob/main/system-parachains/asset-hubs/asset-hub-kusama/src/lib.rs#L810 +impl pallet_nfts::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + + type CollectionId = CollectionId; + type ItemId = MembershipId; + + type Currency = Balances; + type ForceOrigin = EnsureRoot; + // Ensure only root is allowed to executing `create` calls + type CreateOrigin = EnsureRootWithSuccess; + type Locker = (); + + type CollectionDeposit = NftsCollectionDeposit; + type ItemDeposit = NftsItemDeposit; + type MetadataDepositBase = NftsMetadataDepositBase; + type AttributeDepositBase = NftsAttributeDepositBase; + type DepositPerByte = NftsDepositPerByte; + + type StringLimit = ConstU32<256>; + type KeyLimit = ConstU32<64>; + type ValueLimit = ConstU32<256>; + type ApprovalsLimit = ConstU32<20>; + type ItemAttributesApprovalsLimit = ConstU32<30>; + type MaxTips = ConstU32<10>; + type MaxDeadlineDuration = NftsMaxDeadlineDuration; + type MaxAttributesPerCall = ConstU32<10>; + type Features = NftsPalletFeatures; + + type OffchainSignature = Signature; + type OffchainPublic = ::Signer; + type WeightInfo = pallet_nfts::weights::SubstrateWeight; + + #[cfg(feature = "runtime-benchmarks")] + type Helper = NftsBenchmarksHelper; +} + +#[cfg(feature = "runtime-benchmarks")] +pub struct NftsBenchmarksHelper; + +#[cfg(feature = "runtime-benchmarks")] +impl pallet_nfts::BenchmarkHelper for NftsBenchmarksHelper { + fn collection(i: u16) -> CollectionId { + i.into() + } + fn item(i: u16) -> MembershipId { + MembershipId( + ::BenchmarkHelper::community_id(), + i.into(), + ) + } +} diff --git a/runtime/kreivo/src/communities/mod.rs b/runtime/kreivo/src/communities/mod.rs new file mode 100644 index 00000000..285b5743 --- /dev/null +++ b/runtime/kreivo/src/communities/mod.rs @@ -0,0 +1,180 @@ +use super::*; + +use frame_support::traits::{membership::NonFungibleAdpter, tokens::nonfungible_v2::ItemOf}; +use pallet_communities::origin::{EnsureCommunity, EnsureCommunityAccountId}; +use virto_common::{CommunityId, MembershipInfo}; + +pub mod governance; +pub mod memberships; + +parameter_types! { + pub const CommunityPalletId: PalletId = PalletId(*b"kv/cmtys"); + pub const MembershipsCollectionId: memberships::CollectionId = 0; + pub const MembershipNftAttr: &'static [u8; 10] = b"membership"; +} + +type MembershipCollection = ItemOf; +type Memberships = NonFungibleAdpter; + +impl pallet_communities::Config for Runtime { + type CommunityId = CommunityId; + + type CommunityMgmtOrigin = EnsureRoot; + type MemberMgmtOrigin = EitherOf, EnsureCommunityAccountId>; + type MemberMgmt = Memberships; + type Membership = MembershipInfo; + + type Polls = CommunityReferenda; + + type Assets = Assets; + type Balances = Balances; + type RuntimeHoldReason = RuntimeHoldReason; + type RuntimeEvent = RuntimeEvent; + type WeightInfo = pallet_communities::weights::SubstrateWeight; + + type PalletId = CommunityPalletId; + + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = CommunityBenchmarkHelper; +} + +#[cfg(feature = "runtime-benchmarks")] +use self::{ + governance::{CommunityReferendaInstance, CommunityTracksInstance}, + memberships::CommunityMembershipsInstance, +}; + +#[cfg(feature = "runtime-benchmarks")] +use ::{ + frame_benchmarking::BenchmarkError, + frame_support::traits::{schedule::DispatchTime, tokens::nonfungible_v2::Mutate}, + frame_system::pallet_prelude::{OriginFor, RuntimeCallFor}, + pallet_communities::{ + types::{CommunityIdOf, DecisionMethodFor, MembershipIdOf, PalletsOriginOf, PollIndexOf}, + BenchmarkHelper, Origin, + }, + pallet_nfts::Pallet as Nfts, + pallet_referenda::{BoundedCallOf, Curve, Pallet as Referenda, TrackInfo}, + pallet_referenda_tracks::Pallet as Tracks, + sp_core::Encode, + sp_runtime::Perbill, + virto_common::MembershipId, +}; + +#[cfg(feature = "runtime-benchmarks")] +pub struct CommunityBenchmarkHelper; + +#[cfg(feature = "runtime-benchmarks")] +impl BenchmarkHelper for CommunityBenchmarkHelper { + fn community_id() -> CommunityIdOf { + CommunityId::new(1) + } + fn community_desired_size() -> u32 { + u32::MAX + } + fn community_origin(decision_method: DecisionMethodFor) -> OriginFor { + let mut origin = Origin::::new(Self::community_id()); + origin.with_decision_method(decision_method.clone()); + origin.into() + } + + fn membership_id(community_id: CommunityIdOf, index: u32) -> MembershipIdOf { + MembershipId(community_id, index) + } + + fn initialize_memberships_collection() -> Result<(), BenchmarkError> { + let collection = MembershipsCollectionId::get(); + Nfts::::do_create_collection( + collection, + TreasuryAccount::get(), + TreasuryAccount::get(), + Default::default(), + 0, + pallet_nfts::Event::ForceCreated { + collection, + owner: TreasuryAccount::get(), + }, + )?; + + Ok(()) + } + + fn issue_membership( + community_id: CommunityIdOf, + membership_id: MembershipIdOf, + ) -> Result<(), BenchmarkError> { + let community_account = pallet_communities::Pallet::::community_account(&community_id); + MembershipCollection::mint_into(&membership_id, &community_account, &Default::default(), true)?; + + Ok(()) + } + + fn prepare_track(pallet_origin: PalletsOriginOf) -> Result<(), BenchmarkError> { + let id = Self::community_id(); + let info = TrackInfo { + name: sp_runtime::str_array("Community"), + max_deciding: 1, + decision_deposit: 5, + prepare_period: 1, + decision_period: 5, + confirm_period: 1, + min_enactment_period: 1, + min_approval: Curve::LinearDecreasing { + length: Perbill::from_percent(100), + floor: Perbill::from_percent(50), + ceil: Perbill::from_percent(100), + }, + min_support: Curve::LinearDecreasing { + length: Perbill::from_percent(100), + floor: Perbill::from_percent(0), + ceil: Perbill::from_percent(100), + }, + }; + + Tracks::::insert(RuntimeOrigin::root(), id, info, pallet_origin)?; + + Ok(()) + } + + fn prepare_poll( + origin: OriginFor, + proposal_origin: PalletsOriginOf, + proposal_call: RuntimeCallFor, + ) -> Result, BenchmarkError> { + let bounded_call = BoundedVec::truncate_from(proposal_call.encode()); + let proposal_origin = Box::new(proposal_origin); + let proposal = BoundedCallOf::::Inline(bounded_call); + let enactment_moment = DispatchTime::After(1); + + let index = 0u32; + Referenda::::submit( + origin.clone(), + proposal_origin, + proposal, + enactment_moment, + )?; + Referenda::::place_decision_deposit(origin, index)?; + + System::set_block_number(2); + Referenda::::nudge_referendum(RuntimeOrigin::root(), 0)?; + + Ok(0) + } + + fn finish_poll(index: PollIndexOf) -> Result<(), BenchmarkError> { + System::set_block_number(8); + Referenda::::nudge_referendum(RuntimeOrigin::root(), index)?; + + frame_support::assert_ok!(Referenda::::ensure_ongoing(index)); + + System::set_block_number(9); + Referenda::::nudge_referendum(RuntimeOrigin::root(), index)?; + + frame_support::assert_err!( + Referenda::::ensure_ongoing(index), + pallet_referenda::Error::::NotOngoing + ); + + Ok(()) + } +} diff --git a/runtime/kreivo/src/lib.rs b/runtime/kreivo/src/lib.rs index 8bc07ef5..dce9cd25 100644 --- a/runtime/kreivo/src/lib.rs +++ b/runtime/kreivo/src/lib.rs @@ -18,6 +18,7 @@ use parachains_common::message_queue::{NarrowOriginToSibling, ParaIdToSibling}; use polkadot_runtime_common::xcm_sender::NoPriceForMessageDelivery; use sp_api::impl_runtime_apis; use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; +use sp_runtime::traits::Verify; pub use sp_runtime::{ create_runtime_str, generic, impl_opaque_keys, traits::{AccountIdLookup, BlakeTwo256, Block as BlockT, ConvertInto, IdentityLookup}, @@ -32,14 +33,15 @@ use sp_version::RuntimeVersion; use frame_support::{ construct_runtime, derive_impl, dispatch::DispatchClass, + ensure, genesis_builder_helper::{build_config, create_default_config}, parameter_types, traits::{ fungible::HoldConsideration, fungibles, tokens::{PayFromAccount, UnityAssetBalanceConversion}, - AsEnsureOriginWithArg, ConstBool, ConstU32, ConstU64, ConstU8, Contains, EitherOfDiverse, LinearStoragePrice, - NeverEnsureOrigin, TransformOrigin, + AsEnsureOriginWithArg, ConstBool, ConstU32, ConstU64, ConstU8, Contains, EitherOf, EitherOfDiverse, + EnsureOriginWithArg, LinearStoragePrice, NeverEnsureOrigin, TransformOrigin, }, weights::{constants::RocksDbWeight, ConstantMultiplier, Weight}, BoundedVec, PalletId, @@ -52,6 +54,8 @@ use frame_system::{ EnsureRoot, }; +use pallet_nfts::PalletFeatures; + use pallet_xcm::{EnsureXcm, IsVoiceOfBody}; use xcm_config::{RelayLocation, TrustBackedAssetsConvertedConcreteId, XcmOriginToTransactDispatchOrigin}; @@ -63,8 +67,11 @@ pub use polkadot_runtime_common::{prod_or_fast, BlockHashCount, SlowAdjustingFee pub use weights::{BlockExecutionWeight, ExtrinsicBaseWeight}; +// Virto toolchain pub mod payments; +pub mod communities; + // XCM Imports use xcm::latest::prelude::BodyId; @@ -189,6 +196,12 @@ construct_runtime!( // Virto Tooling Payments: pallet_payments = 60, + + // Communities at Kreivo + Communities: pallet_communities = 71, + CommunityTracks: pallet_referenda_tracks:: = 72, + CommunityReferenda: pallet_referenda:: = 73, + CommunityMemberships: pallet_nfts:: = 74, } ); @@ -282,9 +295,9 @@ impl pallet_balances::Config for Runtime { type MaxReserves = ConstU32<50>; type ReserveIdentifier = [u8; 8]; type RuntimeHoldReason = RuntimeHoldReason; - type FreezeIdentifier = (); - type MaxHolds = ConstU32<2>; - type MaxFreezes = ConstU32<0>; + type FreezeIdentifier = RuntimeHoldReason; + type MaxHolds = ConstU32<3>; + type MaxFreezes = ConstU32<256>; type RuntimeFreezeReason = RuntimeFreezeReason; } @@ -631,6 +644,17 @@ impl pallet_preimage::Config for Runtime { >; } +parameter_types! { + pub NftsPalletFeatures: PalletFeatures = PalletFeatures::all_enabled(); + pub const NftsMaxDeadlineDuration: BlockNumber = 12 * 30 * DAYS; + // From https://github.com/polkadot-fellows/runtimes/blob/main/system-parachains/asset-hubs/asset-hub-kusama/src/lib.rs#L745 + pub const NftsCollectionDeposit: Balance = UNITS / 10; + pub const NftsItemDeposit: Balance = UNITS / 1_000; + pub const NftsMetadataDepositBase: Balance = MetadataDepositBase::get(); + pub const NftsAttributeDepositBase: Balance = deposit(1, 0); + pub const NftsDepositPerByte: Balance = MetadataDepositPerByte::get(); +} + parameter_types! { /// The asset ID for the asset that we use to pay for message delivery fees. pub FeeAssetId: cumulus_primitives_core::AssetId = Concrete(xcm_config::RelayLocation::get()); @@ -704,6 +728,10 @@ mod benches { [pallet_proxy, Proxy] [pallet_asset_registry, AssetRegistry] [pallet_payments, Payments] + [pallet_communities, Communities] + [pallet_referenda_tracks, CommunityTracks] + [pallet_referenda, CommunityReferenda] + [pallet_nfts, CommunityMemberships] // XCM // NOTE: Make sure you point to the individual modules below. [pallet_xcm_benchmarks::fungible, XcmBalances] diff --git a/runtime/kreivo/src/payments.rs b/runtime/kreivo/src/payments.rs index 5f267759..5bdbcbba 100644 --- a/runtime/kreivo/src/payments.rs +++ b/runtime/kreivo/src/payments.rs @@ -1,5 +1,8 @@ use super::*; + +use frame_support::traits::EitherOf; use frame_system::EnsureSigned; +use pallet_communities::origin::AsSignedByCommunity; use parity_scale_codec::Encode; parameter_types! { @@ -76,7 +79,7 @@ impl pallet_payments::Config for Runtime { type FeeHandler = KreivoFeeHandler; type IncentivePercentage = IncentivePercentage; type MaxRemarkLength = MaxRemarkLength; - type SenderOrigin = EnsureSigned; + type SenderOrigin = EitherOf, EnsureSigned>; type BeneficiaryOrigin = EnsureSigned; type DisputeResolver = frame_system::EnsureRootWithSuccess; type PalletId = PaymentPalletId; diff --git a/zombienet/kreivo-kusama-local.toml b/zombienet/kreivo-kusama-local.toml index 409d2ce4..30d4f014 100644 --- a/zombienet/kreivo-kusama-local.toml +++ b/zombienet/kreivo-kusama-local.toml @@ -2,29 +2,31 @@ timeout = 1000 [relaychain] -chain = "kusama-local" +chain_spec_path = "./release/kusama-local.raw.json" default_command = "./bin/polkadot" [[relaychain.nodes]] name = "alice" validator = true ws_port = 10000 - extra_args = [ "--force-authoring -lparachain=debug" ] + extra_args = [ + "--force-authoring -lparachain=debug --unsafe-rpc-external --rpc-cors=all", + ] [[relaychain.nodes]] name = "bob" validator = true - extra_args = [ "--force-authoring -lparachain=debug" ] + extra_args = [ "--force-authoring -lparachain=debug --unsafe-rpc-external --rpc-cors=all" ] [[relaychain.nodes]] name = "charlie" validator = true - extra_args = [ "--force-authoring -lparachain=debug" ] + extra_args = [ "--force-authoring -lparachain=debug --unsafe-rpc-external --rpc-cors=all" ] [[relaychain.nodes]] name = "dave" validator = true - extra_args = [ "--force-authoring -lparachain=debug" ] + extra_args = [ "--force-authoring -lparachain=debug --unsafe-rpc-external --rpc-cors=all" ] [[parachains]] id = 2281 diff --git a/zombienet/kreivo-rococo-local.toml b/zombienet/kreivo-rococo-local.toml index 2e215a8c..3bc906f6 100644 --- a/zombienet/kreivo-rococo-local.toml +++ b/zombienet/kreivo-rococo-local.toml @@ -9,22 +9,22 @@ default_command = "./bin/polkadot" name = "alice" validator = true ws_port = 10000 - extra_args = [ "-lparachain=debug" ] + extra_args = [ "--force-authoring -lparachain=debug --unsafe-rpc-external --rpc-cors=all" ] [[relaychain.nodes]] name = "bob" validator = true - extra_args = [ "-lparachain=debug" ] + extra_args = [ "--force-authoring -lparachain=debug --unsafe-rpc-external --rpc-cors=all" ] [[relaychain.nodes]] name = "charlie" validator = true - extra_args = [ "-lparachain=debug" ] + extra_args = [ "--force-authoring -lparachain=debug --unsafe-rpc-external --rpc-cors=all" ] [[relaychain.nodes]] name = "dave" validator = true - extra_args = [ "-lparachain=debug" ] + extra_args = [ "--force-authoring -lparachain=debug --unsafe-rpc-external --rpc-cors=all" ] [[parachains]] id = 2281