From eb25fceb2d8c66e6894214ff42b099d7a5605b40 Mon Sep 17 00:00:00 2001 From: pifragile Date: Thu, 7 Dec 2023 13:12:15 +0800 Subject: [PATCH 01/15] user reputation commitments for recording votes --- Cargo.lock | 1 + democracy/Cargo.toml | 1 + democracy/src/lib.rs | 66 ++++++++++++++------ democracy/src/mock.rs | 2 + democracy/src/tests.rs | 138 ++++++++++++++++++++++------------------- 5 files changed, 123 insertions(+), 85 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ad2bfa7e..2608b67a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4291,6 +4291,7 @@ dependencies = [ "pallet-encointer-balances", "pallet-encointer-ceremonies", "pallet-encointer-communities", + "pallet-encointer-reputation-commitments", "pallet-encointer-scheduler", "pallet-timestamp", "parity-scale-codec", diff --git a/democracy/Cargo.toml b/democracy/Cargo.toml index e5a3b3d0..c6f17c31 100644 --- a/democracy/Cargo.toml +++ b/democracy/Cargo.toml @@ -19,6 +19,7 @@ encointer-ceremonies = { package = "pallet-encointer-ceremonies", path = "../cer encointer-communities = { package = "pallet-encointer-communities", path = "../communities", default-features = false, version = "2.0.0" } encointer-primitives = { path = "../primitives", default-features = false, version = "2.0.0" } encointer-scheduler = { package = "pallet-encointer-scheduler", path = "../scheduler", default-features = false, version = "2.0.0" } +encointer-reputation-commitments = { package = "pallet-encointer-reputation-commitments", path = "../reputation-commitments", default-features = false, version = "2.0.0" } # substrate deps frame-support = { default-features = false, version = "24.0.0" } diff --git a/democracy/src/lib.rs b/democracy/src/lib.rs index 21f76409..07516ae9 100644 --- a/democracy/src/lib.rs +++ b/democracy/src/lib.rs @@ -20,7 +20,7 @@ #![cfg_attr(not(feature = "std"), no_std)] use encointer_primitives::{ - ceremonies::{CommunityCeremony, ReputationCountType}, + ceremonies::ReputationCountType, democracy::{Proposal, ProposalAction, ProposalIdType, ReputationVec}, fixed::{transcendental::sqrt, types::U64F64}, scheduler::{CeremonyIndexType, CeremonyPhaseType}, @@ -45,7 +45,10 @@ type ReputationVecOf = ReputationVec<::MaxReputationVecL #[frame_support::pallet] pub mod pallet { use super::*; - use encointer_primitives::democracy::{Tally, *}; + use encointer_primitives::{ + democracy::{Tally, *}, + reputation_commitments::{DescriptorType, PurposeIdType}, + }; use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; @@ -58,6 +61,7 @@ pub mod pallet { + encointer_scheduler::Config + encointer_ceremonies::Config + encointer_communities::Config + + encointer_reputation_commitments::Config { type RuntimeEvent: From> + IsType<::RuntimeEvent>; @@ -98,8 +102,15 @@ pub mod pallet { AQBError, /// cannot submit new proposal as a proposal of the same type is waiting for enactment ProposalWaitingForEnactment, + /// reputation commitment purpose could not be created + PurposeIdCreationFailed, } + #[pallet::storage] + #[pallet::getter(fn purpose_ids)] + pub(super) type PurposeIds = + StorageMap<_, Blake2_128Concat, ProposalIdType, PurposeIdType, OptionQuery>; + #[pallet::storage] #[pallet::getter(fn proposals)] pub(super) type Proposals = @@ -175,9 +186,20 @@ pub mod pallet { state: ProposalState::Ongoing, action: proposal_action, }; + + let proposal_identifier = + ["democracyProposal".as_bytes(), next_proposal_id.to_string().as_bytes()].concat(); + + let purpose_id = >::do_register_purpose( + DescriptorType::try_from(proposal_identifier) + .map_err(|_| >::PurposeIdCreationFailed)?, + )?; + >::insert(next_proposal_id, proposal); + >::insert(next_proposal_id, purpose_id); >::put(next_proposal_id); >::insert(next_proposal_id, Tally { turnout: 0, ayes: 0 }); + Ok(().into()) } @@ -191,9 +213,9 @@ pub mod pallet { ) -> DispatchResultWithPostInfo { let sender = ensure_signed(origin)?; let tally = >::get(proposal_id).ok_or(Error::::InexistentProposal)?; - let eligible_reputations = - Self::eligible_reputations(proposal_id, &sender, &reputations)?; - let num_votes = eligible_reputations.len() as u128; + + let num_votes = + Self::validate_and_commit_reputations(proposal_id, &sender, &reputations)?; let ayes = match vote { Vote::Aye => num_votes, @@ -209,9 +231,6 @@ pub mod pallet { }; >::insert(proposal_id, new_tally); - for community_ceremony in eligible_reputations { - >::insert(proposal_id, (&sender, community_ceremony), ()); - } match Self::do_update_proposal_state(proposal_id) { Ok(_) => Ok(()), @@ -248,17 +267,17 @@ pub mod pallet { (proposal.start_cindex.saturating_sub(2))) .collect::>()) } - /// Returns the reputations that + /// Validates the reputations based on the following criteria and commits the reputations. Returns count of valid reputations. /// 1. are valid /// 2. have not been used to vote for proposal_id /// 3. originate in the correct community (for Community AccessPolicy) /// 4. are within proposal.start_cindex - reputation_lifetime + proposal_lifetime and proposal.start_cindex - 2 - pub fn eligible_reputations( + pub fn validate_and_commit_reputations( proposal_id: ProposalIdType, account_id: &T::AccountId, reputations: &ReputationVecOf, - ) -> Result, Error> { - let mut eligible_reputations = Vec::::new(); + ) -> Result> { + let mut eligible_reputation_count = 0u128; let maybe_cid = match Self::proposals(proposal_id) .ok_or(Error::::InexistentProposal)? @@ -269,6 +288,9 @@ pub mod pallet { _ => None, }; + let purpose_id = + Self::purpose_ids(proposal_id).ok_or(Error::::InexistentProposal)?; + for community_ceremony in reputations { if !Self::relevant_cindexes(proposal_id)?.contains(&community_ceremony.1) { continue @@ -279,18 +301,22 @@ pub mod pallet { continue } } - if >::contains_key(proposal_id, (account_id, community_ceremony)) { - continue - } - if >::validate_reputation( + + if >::do_commit_reputation( account_id, - &community_ceremony.0, + community_ceremony.0, community_ceremony.1, - ) { - eligible_reputations.push(*community_ceremony); + purpose_id, + None, + ) + .is_err() + { + continue } + + eligible_reputation_count += 1; } - BoundedVec::try_from(eligible_reputations).map_err(|_e| Error::::BoundedVecError) + Ok(eligible_reputation_count) } /// Updates the proposal state diff --git a/democracy/src/mock.rs b/democracy/src/mock.rs index 83f9e5ba..b7927a8e 100644 --- a/democracy/src/mock.rs +++ b/democracy/src/mock.rs @@ -34,6 +34,7 @@ frame_support::construct_runtime!( EncointerCeremonies: encointer_ceremonies::{Pallet, Call, Storage, Event}, EncointerBalances: encointer_balances::{Pallet, Call, Storage, Event}, EncointerDemocracy: dut::{Pallet, Call, Storage, Config, Event}, + EncointerReputationCommitments:encointer_reputation_commitments::{Pallet, Call, Storage, Event}, } ); @@ -58,6 +59,7 @@ impl_encointer_balances!(TestRuntime); impl_encointer_communities!(TestRuntime); impl_encointer_scheduler!(TestRuntime, EncointerDemocracy); impl_encointer_ceremonies!(TestRuntime); +impl_encointer_reputation_commitments!(TestRuntime); // genesis values pub fn new_test_ext() -> sp_io::TestExternalities { diff --git a/democracy/src/tests.rs b/democracy/src/tests.rs index d0df6760..e8013d40 100644 --- a/democracy/src/tests.rs +++ b/democracy/src/tests.rs @@ -137,58 +137,60 @@ fn eligible_reputations_works_with_different_reputations() { )); EncointerCeremonies::fake_reputation((cid, 4), &alice, Reputation::Unverified); - assert!(EncointerDemocracy::eligible_reputations( - 1, - &alice, - &BoundedVec::try_from(vec![(cid, 4)]).unwrap(), - ) - .unwrap() - .is_empty()); + assert_eq!( + EncointerDemocracy::validate_and_commit_reputations( + 1, + &alice, + &BoundedVec::try_from(vec![(cid, 4)]).unwrap(), + ), + Ok(0) + ); EncointerCeremonies::fake_reputation((cid, 5), &alice, Reputation::UnverifiedReputable); - assert!(EncointerDemocracy::eligible_reputations( - 1, - &alice, - &BoundedVec::try_from(vec![(cid, 5)]).unwrap(), - ) - .unwrap() - .is_empty()); + assert_eq!( + EncointerDemocracy::validate_and_commit_reputations( + 1, + &alice, + &BoundedVec::try_from(vec![(cid, 5)]).unwrap(), + ), + Ok(0) + ); EncointerCeremonies::fake_reputation((cid2, 4), &alice, Reputation::VerifiedUnlinked); assert_eq!( - EncointerDemocracy::eligible_reputations( + EncointerDemocracy::validate_and_commit_reputations( 1, &alice, &BoundedVec::try_from(vec![(cid2, 4)]).unwrap(), - ) - .unwrap() - .len(), - 1 + ), + Ok(1) ); EncointerCeremonies::fake_reputation((cid2, 3), &alice, Reputation::VerifiedLinked); assert_eq!( - EncointerDemocracy::eligible_reputations( + EncointerDemocracy::validate_and_commit_reputations( 1, &alice, &BoundedVec::try_from(vec![(cid2, 3)]).unwrap(), - ) - .unwrap() - .len(), - 1 + ), + Ok(1) ); - let eligible_reputations = EncointerDemocracy::eligible_reputations( - 1, - &alice, - &BoundedVec::try_from(vec![(cid, 5), (cid, 4), (cid2, 4), (cid2, 3)]).unwrap(), - ) - .unwrap(); - assert_eq!(eligible_reputations.len(), 2); + let cid3 = register_test_community::(None, 20.0, 10.0); + let cid4 = register_test_community::(None, 10.0, 20.0); - assert_eq!(eligible_reputations.first().unwrap(), &(cid2, 4u32)); - - assert_eq!(eligible_reputations.last().unwrap(), &(cid2, 3u32)); + EncointerCeremonies::fake_reputation((cid3, 4), &alice, Reputation::Unverified); + EncointerCeremonies::fake_reputation((cid3, 5), &alice, Reputation::UnverifiedReputable); + EncointerCeremonies::fake_reputation((cid4, 4), &alice, Reputation::VerifiedUnlinked); + EncointerCeremonies::fake_reputation((cid4, 3), &alice, Reputation::VerifiedLinked); + assert_eq!( + EncointerDemocracy::validate_and_commit_reputations( + 1, + &alice, + &BoundedVec::try_from(vec![(cid3, 5), (cid3, 4), (cid4, 4), (cid4, 3)]).unwrap(), + ), + Ok(2) + ); }); } @@ -205,19 +207,25 @@ fn eligible_reputations_works_with_used_reputations() { )); EncointerCeremonies::fake_reputation((cid, 5), &alice, Reputation::VerifiedLinked); - // use this reputation for a vote - VoteEntries::::insert(1, (alice.clone(), (cid, 5)), ()); - - EncointerCeremonies::fake_reputation((cid, 4), &alice, Reputation::VerifiedLinked); - let eligible_reputations = EncointerDemocracy::eligible_reputations( + // commit reputation + EncointerDemocracy::validate_and_commit_reputations( 1, &alice, - &BoundedVec::try_from(vec![(cid, 5), (cid, 4)]).unwrap(), + &BoundedVec::try_from(vec![(cid, 5)]).unwrap(), + ) + .ok(); + + EncointerCeremonies::fake_reputation((cid, 4), &alice, Reputation::VerifiedLinked); + + assert_eq!( + EncointerDemocracy::validate_and_commit_reputations( + 1, + &alice, + &BoundedVec::try_from(vec![(cid, 5), (cid, 4)]).unwrap(), + ), + Ok(1) ) - .unwrap(); - assert_eq!(eligible_reputations.len(), 1); - assert_eq!(eligible_reputations.first().unwrap().1, 4); }); } @@ -235,14 +243,14 @@ fn eligible_reputations_works_with_inexistent_reputations() { EncointerCeremonies::fake_reputation((cid, 4), &alice, Reputation::VerifiedLinked); - let eligible_reputations = EncointerDemocracy::eligible_reputations( - 1, - &alice, - &BoundedVec::try_from(vec![(cid, 4), (cid, 5)]).unwrap(), + assert_eq!( + EncointerDemocracy::validate_and_commit_reputations( + 1, + &alice, + &BoundedVec::try_from(vec![(cid, 5), (cid, 4)]).unwrap(), + ), + Ok(1) ) - .unwrap(); - assert_eq!(eligible_reputations.len(), 1); - assert_eq!(eligible_reputations.first().unwrap().1, 4); }); } @@ -263,14 +271,14 @@ fn eligible_reputations_works_with_cids() { EncointerCeremonies::fake_reputation((cid, 5), &alice, Reputation::VerifiedLinked); EncointerCeremonies::fake_reputation((cid2, 5), &alice, Reputation::VerifiedLinked); - let eligible_reputations = EncointerDemocracy::eligible_reputations( - 1, - &alice, - &BoundedVec::try_from(vec![(cid, 5), (cid2, 5)]).unwrap(), + assert_eq!( + EncointerDemocracy::validate_and_commit_reputations( + 1, + &alice, + &BoundedVec::try_from(vec![(cid, 5), (cid2, 5)]).unwrap(), + ), + Ok(1) ) - .unwrap(); - assert_eq!(eligible_reputations.len(), 1); - assert_eq!(eligible_reputations.first().unwrap(), &(cid, 5u32)); }); } @@ -290,14 +298,14 @@ fn eligible_reputations_fails_with_invalid_cindex() { EncointerCeremonies::fake_reputation((cid, 4), &alice, Reputation::VerifiedLinked); EncointerCeremonies::fake_reputation((cid, 6), &alice, Reputation::VerifiedLinked); - let eligible_reputations = EncointerDemocracy::eligible_reputations( - 1, - &alice, - &BoundedVec::try_from(vec![(cid, 1), (cid, 4), (cid, 6)]).unwrap(), + assert_eq!( + EncointerDemocracy::validate_and_commit_reputations( + 1, + &alice, + &BoundedVec::try_from(vec![(cid, 1), (cid, 4), (cid, 6)]).unwrap(), + ), + Ok(1) ) - .unwrap(); - assert_eq!(eligible_reputations.len(), 1); - assert_eq!(eligible_reputations.first().unwrap(), &(cid, 4u32)); }); } @@ -352,7 +360,7 @@ fn voting_works() { Vote::Nay, BoundedVec::try_from(vec![ (cid, 2), // invalid beacuse out of range - (cid, 3), // invalid beacuse already used + (cid, 4), // invalid beacuse already used (cid2, 4), // invlaid because unverified (cid2, 5), // valid (cid2, 6), // invlaid because out of range From f24daeb154ea19f54cc24ea179d88a74dc230719 Mon Sep 17 00:00:00 2001 From: pifragile Date: Mon, 11 Dec 2023 14:00:08 +0800 Subject: [PATCH 02/15] use timetstamps instead of blocks --- democracy/src/lib.rs | 66 ++++++++++++++++++++++--------------- democracy/src/mock.rs | 7 ++-- democracy/src/tests.rs | 27 +++++++-------- primitives/src/democracy.rs | 12 +++---- scheduler/src/lib.rs | 10 ++++-- 5 files changed, 71 insertions(+), 51 deletions(-) diff --git a/democracy/src/lib.rs b/democracy/src/lib.rs index 07516ae9..75afab8c 100644 --- a/democracy/src/lib.rs +++ b/democracy/src/lib.rs @@ -26,7 +26,13 @@ use encointer_primitives::{ scheduler::{CeremonyIndexType, CeremonyPhaseType}, }; use encointer_scheduler::OnCeremonyPhaseChange; -use frame_support::traits::Get; +use frame_support::{ + sp_runtime::{ + traits::{CheckedAdd, CheckedDiv, CheckedSub}, + SaturatedConversion, + }, + traits::Get, +}; pub use weights::WeightInfo; #[cfg(not(feature = "std"))] @@ -68,11 +74,9 @@ pub mod pallet { #[pallet::constant] type MaxReputationVecLength: Get; #[pallet::constant] - type ConfirmationPeriod: Get>; - #[pallet::constant] - type ProposalLifetime: Get>; + type ConfirmationPeriod: Get; #[pallet::constant] - type ProposalLifetimeCycles: Get; // ceil of the proposal lifetime in cycles + type ProposalLifetime: Get; #[pallet::constant] type MinTurnout: Get; // in permill type WeightInfo: WeightInfo; @@ -104,6 +108,8 @@ pub mod pallet { ProposalWaitingForEnactment, /// reputation commitment purpose could not be created PurposeIdCreationFailed, + /// error when doing math operations + MathError, } #[pallet::storage] @@ -114,7 +120,7 @@ pub mod pallet { #[pallet::storage] #[pallet::getter(fn proposals)] pub(super) type Proposals = - StorageMap<_, Blake2_128Concat, ProposalIdType, Proposal>, OptionQuery>; + StorageMap<_, Blake2_128Concat, ProposalIdType, Proposal, OptionQuery>; #[pallet::storage] #[pallet::getter(fn proposal_count)] @@ -138,9 +144,9 @@ pub mod pallet { >; // TODO set default value #[pallet::storage] - #[pallet::getter(fn cancelled_at_block)] - pub(super) type CancelledAtBlock = - StorageMap<_, Blake2_128Concat, ProposalActionIdentifier, BlockNumberFor, ValueQuery>; + #[pallet::getter(fn cancelled_at)] + pub(super) type CancelledAt = + StorageMap<_, Blake2_128Concat, ProposalActionIdentifier, T::Moment, ValueQuery>; #[pallet::storage] #[pallet::getter(fn enactment_queue)] @@ -179,9 +185,9 @@ pub mod pallet { let next_proposal_id = current_proposal_id .checked_add(1u128) .ok_or(Error::::ProposalIdOutOfBounds)?; - let current_block = frame_system::Pallet::::block_number(); + let now = >::get(); let proposal = Proposal { - start: current_block, + start: now, start_cindex: cindex, state: ProposalState::Ongoing, action: proposal_action, @@ -260,11 +266,22 @@ pub mod pallet { ) -> Result, Error> { let reputation_lifetime = >::reputation_lifetime(); let proposal = Self::proposals(proposal_id).ok_or(Error::::InexistentProposal)?; - Ok(((proposal - .start_cindex - .saturating_sub(reputation_lifetime) - .saturating_add(T::ProposalLifetimeCycles::get()))..= - (proposal.start_cindex.saturating_sub(2))) + let cycle_duration = >::get_cycle_duration(); + let proposal_lifetime = T::ProposalLifetime::get(); + // ceil(proposal_lifetime / cycle_duration) + let proposal_lifetime_cycles: u64 = proposal_lifetime + .checked_add(&cycle_duration) + .ok_or(Error::::MathError)? + .checked_sub(&T::Moment::saturated_from(1u64)) + .ok_or(Error::::MathError)? + .checked_div(&cycle_duration) + .ok_or(Error::::MathError)? + .saturated_into(); + Ok((((proposal.start_cindex).saturating_sub(reputation_lifetime).saturating_add( + proposal_lifetime_cycles + .try_into() + .expect("this is a small number in cycles;qed"), + ))..=(proposal.start_cindex.saturating_sub(2u32))) .collect::>()) } /// Validates the reputations based on the following criteria and commits the reputations. Returns count of valid reputations. @@ -327,11 +344,11 @@ pub mod pallet { Self::proposals(proposal_id).ok_or(Error::::InexistentProposal)?; ensure!(proposal.state.can_update(), Error::::ProposalCannotBeUpdated); let mut approved = false; - let current_block = frame_system::Pallet::::block_number(); + let now = >::get(); let proposal_action_identifier = proposal.action.get_identifier(); - let cancelled_at_block = Self::cancelled_at_block(proposal_action_identifier); - let proposal_cancelled = proposal.start < cancelled_at_block; - let proposal_too_old = current_block - proposal.start > T::ProposalLifetime::get(); + let cancelled_at = Self::cancelled_at(proposal_action_identifier); + let proposal_cancelled = proposal.start < cancelled_at; + let proposal_too_old = now - proposal.start > T::ProposalLifetime::get(); if proposal_cancelled || proposal_too_old { proposal.state = ProposalState::Cancelled; } else { @@ -340,18 +357,15 @@ pub mod pallet { // confirming if let ProposalState::Confirming { since } = proposal.state { // confirmed longer than period - if current_block - since > T::ConfirmationPeriod::get() { + if now - since > T::ConfirmationPeriod::get() { proposal.state = ProposalState::Approved; >::insert(proposal_action_identifier, proposal_id); - >::insert( - proposal_action_identifier, - current_block, - ); + >::insert(proposal_action_identifier, now); approved = true; } // not confirming } else { - proposal.state = ProposalState::Confirming { since: current_block }; + proposal.state = ProposalState::Confirming { since: now }; } // not passing } else { diff --git a/democracy/src/mock.rs b/democracy/src/mock.rs index b7927a8e..6039a953 100644 --- a/democracy/src/mock.rs +++ b/democracy/src/mock.rs @@ -45,9 +45,10 @@ frame_support::construct_runtime!( impl dut::Config for TestRuntime { type RuntimeEvent = RuntimeEvent; type MaxReputationVecLength = ConstU32<10>; - type ConfirmationPeriod = ConstU64<10>; - type ProposalLifetime = ConstU64<40>; - type ProposalLifetimeCycles = ConstU32<1>; + // 10 blocks + type ConfirmationPeriod = ConstU64<60000>; + // 40 blocks + type ProposalLifetime = ConstU64<240000>; type MinTurnout = ConstU128<20>; // 2% type WeightInfo = (); // 2% } diff --git a/democracy/src/tests.rs b/democracy/src/tests.rs index e8013d40..badb6c2a 100644 --- a/democracy/src/tests.rs +++ b/democracy/src/tests.rs @@ -47,12 +47,11 @@ fn bob() -> AccountId { AccountKeyring::Bob.into() } -type BlockNumber = BlockNumberFor; - fn advance_n_blocks(n: u64) { + let mut blocknr = System::block_number(); for _ in 0..n { - System::set_block_number(System::block_number() + 1); - System::on_initialize(System::block_number()); + blocknr += 1; + run_to_block(blocknr); } } @@ -66,7 +65,7 @@ fn run_to_block(n: u64) { if System::block_number() > 1 { System::on_finalize(System::block_number()); } - set_timestamp(GENESIS_TIME + BLOCKTIME * n); + set_timestamp(Timestamp::get() + BLOCKTIME); Timestamp::on_finalize(System::block_number()); System::set_block_number(System::block_number() + 1); System::on_initialize(System::block_number()); @@ -87,7 +86,7 @@ fn run_to_next_phase() { fn proposal_submission_works() { new_test_ext().execute_with(|| { let cid = create_cid(); - let block = System::block_number(); + let now = Timestamp::get(); let proposal_action = ProposalAction::UpdateNominalIncome(cid, NominalIncomeType::from(100u32)); @@ -99,7 +98,7 @@ fn proposal_submission_works() { let proposal = EncointerDemocracy::proposals(1).unwrap(); assert_eq!(proposal.state, ProposalState::Ongoing); assert_eq!(proposal.action, proposal_action); - assert_eq!(proposal.start, block); + assert_eq!(proposal.start, now); assert!(EncointerDemocracy::tallies(1).is_some()); }); } @@ -389,16 +388,16 @@ fn do_update_proposal_state_fails_with_inexistent_proposal() { fn do_update_proposal_state_fails_with_wrong_state() { new_test_ext().execute_with(|| { let cid = create_cid(); - let proposal: Proposal = Proposal { - start: BlockNumber::from(1u64), + let proposal: Proposal = Proposal { + start: Moment::from(1u64), start_cindex: 1, action: ProposalAction::UpdateNominalIncome(cid, NominalIncomeType::from(100u32)), state: ProposalState::Cancelled, }; Proposals::::insert(1, proposal); - let proposal2: Proposal = Proposal { - start: BlockNumber::from(1u64), + let proposal2: Proposal = Proposal { + start: Moment::from(1u64), start_cindex: 1, action: ProposalAction::UpdateNominalIncome(cid, NominalIncomeType::from(100u32)), state: ProposalState::Approved, @@ -427,7 +426,10 @@ fn do_update_proposal_state_works_with_cancelled_proposal() { proposal_action )); - CancelledAtBlock::::insert(ProposalActionIdentifier::SetInactivityTimeout, 3); + CancelledAt::::insert( + ProposalActionIdentifier::SetInactivityTimeout, + 3 * BLOCKTIME, + ); assert_eq!(EncointerDemocracy::proposals(1).unwrap().state, ProposalState::Ongoing); @@ -455,7 +457,6 @@ fn do_update_proposal_state_works_with_too_old_proposal() { assert_ok!(EncointerDemocracy::do_update_proposal_state(1)); assert_eq!(EncointerDemocracy::proposals(1).unwrap().state, ProposalState::Ongoing); - advance_n_blocks(1); assert_ok!(EncointerDemocracy::do_update_proposal_state(1)); diff --git a/primitives/src/democracy.rs b/primitives/src/democracy.rs index 9fe88183..33269f4b 100644 --- a/primitives/src/democracy.rs +++ b/primitives/src/democracy.rs @@ -79,15 +79,15 @@ impl ProposalAction { #[derive(Encode, Decode, RuntimeDebug, Clone, Copy, PartialEq, Eq, TypeInfo, MaxEncodedLen)] #[cfg_attr(feature = "serde_derive", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde_derive", serde(rename_all = "camelCase"))] -pub enum ProposalState { +pub enum ProposalState { Ongoing, - Confirming { since: BlockNumber }, + Confirming { since: Moment }, Approved, Cancelled, Enacted, } -impl ProposalState { +impl ProposalState { pub fn can_update(self) -> bool { matches!(self, Self::Confirming { since: _ } | Self::Ongoing) } @@ -95,9 +95,9 @@ impl ProposalState { #[derive(Encode, Decode, RuntimeDebug, Clone, PartialEq, Eq, TypeInfo, MaxEncodedLen)] #[cfg_attr(feature = "serde_derive", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde_derive", serde(rename_all = "camelCase"))] -pub struct Proposal { - pub start: BlockNumber, +pub struct Proposal { + pub start: Moment, pub start_cindex: CeremonyIndexType, pub action: ProposalAction, - pub state: ProposalState, + pub state: ProposalState, } diff --git a/scheduler/src/lib.rs b/scheduler/src/lib.rs index 5f426956..8ccf3b20 100644 --- a/scheduler/src/lib.rs +++ b/scheduler/src/lib.rs @@ -223,13 +223,17 @@ impl Pallet { Ok(()) } + pub fn get_cycle_duration() -> T::Moment { + >::get(CeremonyPhaseType::Registering) + + >::get(CeremonyPhaseType::Assigning) + + >::get(CeremonyPhaseType::Attesting) + } + // we need to resync in two situations: // 1. when the chain bootstraps and cycle duration is smaller than 24h, phases would cycle with every block until catched up // 2. when next_phase() is used, we would introduce long idle phases because next_phase_timestamp would be pushed furhter and further into the future fn resync_and_set_next_phase_timestamp(tnext: T::Moment) -> DispatchResult { - let cycle_duration = >::get(CeremonyPhaseType::Registering) + - >::get(CeremonyPhaseType::Assigning) + - >::get(CeremonyPhaseType::Attesting); + let cycle_duration = Self::get_cycle_duration(); let now = >::now(); let tnext = if tnext < now { From 59422ba5ca6fd61fd6c815a3cf4e14835ce9a4dc Mon Sep 17 00:00:00 2001 From: pifragile Date: Tue, 12 Dec 2023 09:47:55 +0800 Subject: [PATCH 03/15] add more events --- democracy/src/lib.rs | 40 +++++++++++++++++++++++++++++++++++++--- democracy/src/tests.rs | 39 ++++++++++++++++++++++++++++++++++++--- 2 files changed, 73 insertions(+), 6 deletions(-) diff --git a/democracy/src/lib.rs b/democracy/src/lib.rs index 75afab8c..fe96d1b6 100644 --- a/democracy/src/lib.rs +++ b/democracy/src/lib.rs @@ -86,7 +86,26 @@ pub mod pallet { #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { /// proposal enacted - ProposalEnacted { proposal_id: ProposalIdType }, + ProposalEnacted { + proposal_id: ProposalIdType, + }, + ProposalSubmitted { + proposal_id: ProposalIdType, + proposal_action: ProposalAction, + }, + VotePlaced { + proposal_id: ProposalIdType, + vote: Vote, + num_votes: u128, + }, + VoteFailed { + proposal_id: ProposalIdType, + vote: Vote, + }, + ProposalStateUpdated { + proposal_id: ProposalIdType, + proposal_state: ProposalState, + }, } #[pallet::error] @@ -205,7 +224,10 @@ pub mod pallet { >::insert(next_proposal_id, purpose_id); >::put(next_proposal_id); >::insert(next_proposal_id, Tally { turnout: 0, ayes: 0 }); - + Self::deposit_event(Event::ProposalSubmitted { + proposal_id: next_proposal_id, + proposal_action, + }); Ok(().into()) } @@ -246,6 +268,11 @@ pub mod pallet { }, }?; + if num_votes > 0 { + Self::deposit_event(Event::VotePlaced { proposal_id, vote, num_votes }) + } else { + Self::deposit_event(Event::VoteFailed { proposal_id, vote }) + } Ok(().into()) } @@ -344,6 +371,7 @@ pub mod pallet { Self::proposals(proposal_id).ok_or(Error::::InexistentProposal)?; ensure!(proposal.state.can_update(), Error::::ProposalCannotBeUpdated); let mut approved = false; + let old_proposal_state = proposal.state; let now = >::get(); let proposal_action_identifier = proposal.action.get_identifier(); let cancelled_at = Self::cancelled_at(proposal_action_identifier); @@ -375,7 +403,13 @@ pub mod pallet { } } } - >::insert(proposal_id, proposal); + >::insert(proposal_id, &proposal); + if old_proposal_state != proposal.state { + Self::deposit_event(Event::ProposalStateUpdated { + proposal_id, + proposal_state: proposal.state, + }); + } Ok(approved) } diff --git a/democracy/src/tests.rs b/democracy/src/tests.rs index badb6c2a..e4068b77 100644 --- a/democracy/src/tests.rs +++ b/democracy/src/tests.rs @@ -27,11 +27,13 @@ use frame_support::{ assert_err, assert_ok, traits::{OnFinalize, OnInitialize}, }; -use frame_system::pallet_prelude::BlockNumberFor; use mock::{new_test_ext, EncointerDemocracy, RuntimeOrigin, System, TestRuntime}; use sp_runtime::BoundedVec; use test_utils::{ - helpers::{account_id, add_population, register_test_community}, + helpers::{ + account_id, add_population, event_at_index, get_num_events, last_event, + register_test_community, + }, *, }; @@ -673,6 +675,7 @@ fn enactment_updates_proposal_metadata_and_enactment_queue() { #[test] fn proposal_happy_flow() { new_test_ext().execute_with(|| { + System::set_block_number(System::block_number() + 1); // this is needed to assert events let cid = create_cid(); let cid2 = register_test_community::(None, 10.0, 10.0); let alice = alice(); @@ -680,8 +683,12 @@ fn proposal_happy_flow() { ProposalAction::UpdateNominalIncome(cid, NominalIncomeType::from(13037u32)); assert_ok!(EncointerDemocracy::submit_proposal( RuntimeOrigin::signed(alice.clone()), - proposal_action + proposal_action.clone() )); + assert_eq!( + last_event::(), + Some(Event::ProposalSubmitted { proposal_id: 1, proposal_action }.into()) + ); EncointerCeremonies::fake_reputation((cid, 3), &alice, Reputation::VerifiedLinked); EncointerCeremonies::fake_reputation((cid, 4), &alice, Reputation::VerifiedLinked); @@ -695,6 +702,11 @@ fn proposal_happy_flow() { BoundedVec::try_from(vec![(cid, 3), (cid, 4), (cid, 5),]).unwrap() )); + assert_eq!( + last_event::(), + Some(Event::VotePlaced { proposal_id: 1, vote: Vote::Aye, num_votes: 3 }.into()) + ); + advance_n_blocks(40); assert_ok!(EncointerDemocracy::vote( RuntimeOrigin::signed(alice.clone()), @@ -703,10 +715,31 @@ fn proposal_happy_flow() { BoundedVec::try_from(vec![(cid2, 3)]).unwrap() )); + assert_eq!( + last_event::(), + Some(Event::VoteFailed { proposal_id: 1, vote: Vote::Aye }.into()) + ); + + assert_eq!( + event_at_index::(get_num_events::() - 2), + Some( + Event::ProposalStateUpdated { + proposal_id: 1, + proposal_state: ProposalState::Approved + } + .into() + ) + ); + run_to_next_phase(); run_to_next_phase(); run_to_next_phase(); + assert_eq!( + event_at_index::(get_num_events::() - 2), + Some(Event::ProposalEnacted { proposal_id: 1 }.into()) + ); + assert_eq!(EncointerDemocracy::proposals(1).unwrap().state, ProposalState::Enacted); assert_eq!(EncointerDemocracy::enactment_queue(proposal_action.get_identifier()), None); assert_eq!(EncointerCommunities::nominal_income(cid), NominalIncomeType::from(13037u32)); From 509d148ddaf10318d9e879cc1861897286c908a0 Mon Sep 17 00:00:00 2001 From: pifragile Date: Thu, 14 Dec 2023 07:29:11 +0800 Subject: [PATCH 04/15] add more events --- democracy/src/lib.rs | 33 +++++++++++++------------------ democracy/src/tests.rs | 39 ++++++++++++++++++++++++------------- primitives/src/democracy.rs | 4 ++-- 3 files changed, 42 insertions(+), 34 deletions(-) diff --git a/democracy/src/lib.rs b/democracy/src/lib.rs index fe96d1b6..6dd9939c 100644 --- a/democracy/src/lib.rs +++ b/democracy/src/lib.rs @@ -210,6 +210,7 @@ pub mod pallet { start_cindex: cindex, state: ProposalState::Ongoing, action: proposal_action, + electorate_size: Self::get_electorate(cindex, proposal_action)?, }; let proposal_identifier = @@ -289,10 +290,9 @@ pub mod pallet { } impl Pallet { fn relevant_cindexes( - proposal_id: ProposalIdType, + start_cindex: CeremonyIndexType, ) -> Result, Error> { let reputation_lifetime = >::reputation_lifetime(); - let proposal = Self::proposals(proposal_id).ok_or(Error::::InexistentProposal)?; let cycle_duration = >::get_cycle_duration(); let proposal_lifetime = T::ProposalLifetime::get(); // ceil(proposal_lifetime / cycle_duration) @@ -304,11 +304,11 @@ pub mod pallet { .checked_div(&cycle_duration) .ok_or(Error::::MathError)? .saturated_into(); - Ok((((proposal.start_cindex).saturating_sub(reputation_lifetime).saturating_add( + Ok((((start_cindex).saturating_sub(reputation_lifetime).saturating_add( proposal_lifetime_cycles .try_into() .expect("this is a small number in cycles;qed"), - ))..=(proposal.start_cindex.saturating_sub(2u32))) + ))..=(start_cindex.saturating_sub(2u32))) .collect::>()) } /// Validates the reputations based on the following criteria and commits the reputations. Returns count of valid reputations. @@ -322,12 +322,8 @@ pub mod pallet { reputations: &ReputationVecOf, ) -> Result> { let mut eligible_reputation_count = 0u128; - - let maybe_cid = match Self::proposals(proposal_id) - .ok_or(Error::::InexistentProposal)? - .action - .get_access_policy() - { + let proposal = Self::proposals(proposal_id).ok_or(Error::::InexistentProposal)?; + let maybe_cid = match proposal.action.get_access_policy() { ProposalAccessPolicy::Community(cid) => Some(cid), _ => None, }; @@ -336,7 +332,8 @@ pub mod pallet { Self::purpose_ids(proposal_id).ok_or(Error::::InexistentProposal)?; for community_ceremony in reputations { - if !Self::relevant_cindexes(proposal_id)?.contains(&community_ceremony.1) { + if !Self::relevant_cindexes(proposal.start_cindex)?.contains(&community_ceremony.1) + { continue } @@ -414,14 +411,11 @@ pub mod pallet { } pub fn get_electorate( - proposal_id: ProposalIdType, + start_cindex: CeremonyIndexType, + proposal_action: ProposalAction, ) -> Result> { - let relevant_cindexes = Self::relevant_cindexes(proposal_id)?; - match Self::proposals(proposal_id) - .ok_or(Error::::InexistentProposal)? - .action - .get_access_policy() - { + let relevant_cindexes = Self::relevant_cindexes(start_cindex)?; + match proposal_action.get_access_policy() { ProposalAccessPolicy::Community(cid) => Ok(relevant_cindexes .into_iter() .map(|cindex| { @@ -469,7 +463,8 @@ pub mod pallet { pub fn is_passing(proposal_id: ProposalIdType) -> Result> { let tally = Self::tallies(proposal_id).ok_or(Error::::InexistentProposal)?; - let electorate = Self::get_electorate(proposal_id)?; + let proposal = Self::proposals(proposal_id).ok_or(Error::::InexistentProposal)?; + let electorate = proposal.electorate_size; let turnout_permill = (tally.turnout * 1000).checked_div(electorate).unwrap_or(0); if turnout_permill < T::MinTurnout::get() { diff --git a/democracy/src/tests.rs b/democracy/src/tests.rs index e4068b77..4d5f404c 100644 --- a/democracy/src/tests.rs +++ b/democracy/src/tests.rs @@ -89,11 +89,20 @@ fn proposal_submission_works() { new_test_ext().execute_with(|| { let cid = create_cid(); let now = Timestamp::get(); + let alice = alice(); + + // invalid + EncointerCeremonies::fake_reputation((cid, 2), &alice, Reputation::VerifiedLinked); + // valid + EncointerCeremonies::fake_reputation((cid, 3), &alice, Reputation::VerifiedLinked); + EncointerCeremonies::fake_reputation((cid, 4), &alice, Reputation::VerifiedLinked); + EncointerCeremonies::fake_reputation((cid, 5), &alice, Reputation::VerifiedLinked); + let proposal_action = ProposalAction::UpdateNominalIncome(cid, NominalIncomeType::from(100u32)); assert_ok!(EncointerDemocracy::submit_proposal( - RuntimeOrigin::signed(alice()), + RuntimeOrigin::signed(alice), proposal_action.clone() )); assert_eq!(EncointerDemocracy::proposal_count(), 1); @@ -101,6 +110,7 @@ fn proposal_submission_works() { assert_eq!(proposal.state, ProposalState::Ongoing); assert_eq!(proposal.action, proposal_action); assert_eq!(proposal.start, now); + assert_eq!(proposal.electorate_size, 3); assert!(EncointerDemocracy::tallies(1).is_some()); }); } @@ -395,6 +405,7 @@ fn do_update_proposal_state_fails_with_wrong_state() { start_cindex: 1, action: ProposalAction::UpdateNominalIncome(cid, NominalIncomeType::from(100u32)), state: ProposalState::Cancelled, + electorate_size: 0, }; Proposals::::insert(1, proposal); @@ -403,6 +414,7 @@ fn do_update_proposal_state_fails_with_wrong_state() { start_cindex: 1, action: ProposalAction::UpdateNominalIncome(cid, NominalIncomeType::from(100u32)), state: ProposalState::Approved, + electorate_size: 0, }; Proposals::::insert(2, proposal2); @@ -540,15 +552,15 @@ fn update_proposal_state_extrinsic_works() { let alice = alice(); let cid = register_test_community::(None, 10.0, 10.0); + EncointerCeremonies::fake_reputation((cid, 3), &alice, Reputation::VerifiedLinked); + EncointerCeremonies::fake_reputation((cid, 4), &alice, Reputation::VerifiedLinked); + EncointerCeremonies::fake_reputation((cid, 5), &alice, Reputation::VerifiedLinked); + assert_ok!(EncointerDemocracy::submit_proposal( RuntimeOrigin::signed(alice.clone()), proposal_action )); - EncointerCeremonies::fake_reputation((cid, 3), &alice, Reputation::VerifiedLinked); - EncointerCeremonies::fake_reputation((cid, 4), &alice, Reputation::VerifiedLinked); - EncointerCeremonies::fake_reputation((cid, 5), &alice, Reputation::VerifiedLinked); - assert_eq!(EncointerDemocracy::proposals(1).unwrap().state, ProposalState::Ongoing); // propsal is passing Tallies::::insert(1, Tally { turnout: 3, ayes: 3 }); @@ -580,15 +592,15 @@ fn test_get_electorate_works() { proposal_action )); - let proposal_action = + let proposal_action2 = ProposalAction::UpdateNominalIncome(cid, NominalIncomeType::from(100u32)); assert_ok!(EncointerDemocracy::submit_proposal( RuntimeOrigin::signed(alice.clone()), proposal_action )); - assert_eq!(EncointerDemocracy::get_electorate(1).unwrap(), 5); - assert_eq!(EncointerDemocracy::get_electorate(2).unwrap(), 2); + assert_eq!(EncointerDemocracy::get_electorate(7, proposal_action).unwrap(), 5); + assert_eq!(EncointerDemocracy::get_electorate(7, proposal_action2).unwrap(), 2); }); } @@ -679,6 +691,12 @@ fn proposal_happy_flow() { let cid = create_cid(); let cid2 = register_test_community::(None, 10.0, 10.0); let alice = alice(); + + EncointerCeremonies::fake_reputation((cid, 3), &alice, Reputation::VerifiedLinked); + EncointerCeremonies::fake_reputation((cid, 4), &alice, Reputation::VerifiedLinked); + EncointerCeremonies::fake_reputation((cid, 5), &alice, Reputation::VerifiedLinked); + EncointerCeremonies::fake_reputation((cid2, 3), &alice, Reputation::VerifiedLinked); + let proposal_action = ProposalAction::UpdateNominalIncome(cid, NominalIncomeType::from(13037u32)); assert_ok!(EncointerDemocracy::submit_proposal( @@ -690,11 +708,6 @@ fn proposal_happy_flow() { Some(Event::ProposalSubmitted { proposal_id: 1, proposal_action }.into()) ); - EncointerCeremonies::fake_reputation((cid, 3), &alice, Reputation::VerifiedLinked); - EncointerCeremonies::fake_reputation((cid, 4), &alice, Reputation::VerifiedLinked); - EncointerCeremonies::fake_reputation((cid, 5), &alice, Reputation::VerifiedLinked); - EncointerCeremonies::fake_reputation((cid2, 3), &alice, Reputation::VerifiedLinked); - assert_ok!(EncointerDemocracy::vote( RuntimeOrigin::signed(alice.clone()), 1, diff --git a/primitives/src/democracy.rs b/primitives/src/democracy.rs index 33269f4b..80707de2 100644 --- a/primitives/src/democracy.rs +++ b/primitives/src/democracy.rs @@ -5,12 +5,11 @@ use crate::{ use codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; -use crate::scheduler::CeremonyIndexType; +use crate::{ceremonies::ReputationCountType, scheduler::CeremonyIndexType}; #[cfg(feature = "serde_derive")] use serde::{Deserialize, Serialize}; use sp_core::RuntimeDebug; use sp_runtime::BoundedVec; - pub type ProposalIdType = u128; pub type VoteCountType = u128; pub type VoteEntry = (AccountId, CommunityCeremony); @@ -100,4 +99,5 @@ pub struct Proposal { pub start_cindex: CeremonyIndexType, pub action: ProposalAction, pub state: ProposalState, + pub electorate_size: ReputationCountType, } From 8cd6183f8279561c69d47b6daf52ec7d33e57b39 Mon Sep 17 00:00:00 2001 From: pifragile Date: Mon, 18 Dec 2023 12:25:49 +0800 Subject: [PATCH 05/15] pass enactment errors to user --- democracy/src/lib.rs | 22 +++++++++++++++------- democracy/src/tests.rs | 30 ++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 7 deletions(-) diff --git a/democracy/src/lib.rs b/democracy/src/lib.rs index 6dd9939c..b669107c 100644 --- a/democracy/src/lib.rs +++ b/democracy/src/lib.rs @@ -25,6 +25,8 @@ use encointer_primitives::{ fixed::{transcendental::sqrt, types::U64F64}, scheduler::{CeremonyIndexType, CeremonyPhaseType}, }; +use frame_support::dispatch::DispatchErrorWithPostInfo; + use encointer_scheduler::OnCeremonyPhaseChange; use frame_support::{ sp_runtime::{ @@ -106,6 +108,10 @@ pub mod pallet { proposal_id: ProposalIdType, proposal_state: ProposalState, }, + EnactmentFailed { + proposal_id: ProposalIdType, + reason: DispatchErrorWithPostInfo, + }, } #[pallet::error] @@ -479,29 +485,29 @@ pub mod pallet { } Ok(false) } - pub fn enact_proposal(proposal_id: ProposalIdType) -> Result<(), Error> { + pub fn enact_proposal(proposal_id: ProposalIdType) -> DispatchResultWithPostInfo { let mut proposal = Self::proposals(proposal_id).ok_or(Error::::InexistentProposal)?; match proposal.action { ProposalAction::UpdateNominalIncome(cid, nominal_income) => { - let _ = >::do_update_nominal_income( + >::do_update_nominal_income( cid, nominal_income, - ); + )?; }, ProposalAction::SetInactivityTimeout(inactivity_timeout) => { - let _ = >::do_set_inactivity_timeout( + >::do_set_inactivity_timeout( inactivity_timeout, - ); + )?; }, }; proposal.state = ProposalState::Enacted; >::insert(proposal_id, proposal); Self::deposit_event(Event::ProposalEnacted { proposal_id }); - Ok(()) + Ok(().into()) } } } @@ -514,7 +520,9 @@ impl OnCeremonyPhaseChange for Pallet { CeremonyPhaseType::Registering => { // safe as EnactmentQueue has one key per ProposalActionType and those are bounded >::iter().for_each(|p| { - let _ = Self::enact_proposal(p.1); + if let Err(e) = Self::enact_proposal(p.1) { + Self::deposit_event(Event::EnactmentFailed { proposal_id: p.1, reason: e }) + } }); // remove all keys from the map >::translate::(|_, _| None); diff --git a/democracy/src/tests.rs b/democracy/src/tests.rs index 4d5f404c..2ead9905 100644 --- a/democracy/src/tests.rs +++ b/democracy/src/tests.rs @@ -29,6 +29,7 @@ use frame_support::{ }; use mock::{new_test_ext, EncointerDemocracy, RuntimeOrigin, System, TestRuntime}; use sp_runtime::BoundedVec; +use std::str::FromStr; use test_utils::{ helpers::{ account_id, add_population, event_at_index, get_num_events, last_event, @@ -808,3 +809,32 @@ fn enact_set_inactivity_timeout_works() { ); }); } + +#[test] +fn enactment_error_fires_event() { + new_test_ext().execute_with(|| { + System::set_block_number(System::block_number() + 1); // this is needed to assert events + let cid = CommunityIdentifier::from_str("u0qj944rhWE").unwrap(); + let alice = alice(); + let proposal_action = + ProposalAction::UpdateNominalIncome(cid, NominalIncomeType::from(13037u32)); + assert_ok!(EncointerDemocracy::submit_proposal( + RuntimeOrigin::signed(alice.clone()), + proposal_action + )); + + EnactmentQueue::::insert(proposal_action.get_identifier(), 1); + + run_to_next_phase(); + run_to_next_phase(); + run_to_next_phase(); + + match event_at_index::(get_num_events::() - 2).unwrap() { + mock::RuntimeEvent::EncointerDemocracy(Event::EnactmentFailed { + proposal_id: 1, + reason: _r, + }) => (), + _ => panic!("Wrong event"), + }; + }); +} From bd0751df96e4fb6862cc9487588cd4cc9781d2cf Mon Sep 17 00:00:00 2001 From: pifragile Date: Wed, 20 Dec 2023 15:49:44 +0800 Subject: [PATCH 06/15] implement proposal actions --- communities/src/lib.rs | 168 +++++++++++++++++++--------------- democracy/src/lib.rs | 25 ++++- democracy/src/tests.rs | 178 +++++++++++++++++++++++++++++++----- primitives/src/democracy.rs | 26 +++++- 4 files changed, 295 insertions(+), 102 deletions(-) diff --git a/communities/src/lib.rs b/communities/src/lib.rs index 5185fc44..87eb87c4 100644 --- a/communities/src/lib.rs +++ b/communities/src/lib.rs @@ -173,41 +173,7 @@ pub mod pallet { location: Location, ) -> DispatchResultWithPostInfo { T::TrustableForNonDestructiveAction::ensure_origin(origin)?; - - ensure!( - >::current_phase() == CeremonyPhaseType::Registering, - Error::::RegistrationPhaseRequired - ); - Self::ensure_cid_exists(&cid)?; - Self::validate_location(&location)?; - let geo_hash = GeoHash::try_from_params(location.lat, location.lon) - .map_err(|_| >::InvalidLocationForGeohash)?; - // insert location into locations - let mut locations = Self::locations(cid, &geo_hash); - match locations.binary_search(&location) { - Ok(_) => (), - Err(index) => { - locations - .try_insert(index, location) - .map_err(|_| Error::::TooManyLocationsPerGeohash)?; - >::insert(cid, &geo_hash, locations); - }, - } - // check if cid is in cids_by_geohash, if not, add it - let mut cids = Self::cids_by_geohash(&geo_hash); - match cids.binary_search(&cid) { - Ok(_) => (), - Err(index) => { - cids.try_insert(index, cid) - .map_err(|_| Error::::TooManyCommunityIdentifiersPerGeohash)?; - >::insert(&geo_hash, cids); - }, - } - sp_io::offchain_index::set(CACHE_DIRTY_KEY, &true.encode()); - - info!(target: LOG, "added location {:?} to community with cid: {:?}", location, cid); - Self::deposit_event(Event::LocationAdded(cid, location)); - Ok(().into()) + Self::do_add_loaction(cid, location) } /// Remove an existing meetup `location` from the community with `cid`. @@ -223,19 +189,7 @@ pub mod pallet { location: Location, ) -> DispatchResultWithPostInfo { T::CommunityMaster::ensure_origin(origin)?; - - ensure!( - >::current_phase() == CeremonyPhaseType::Registering, - Error::::RegistrationPhaseRequired - ); - Self::ensure_cid_exists(&cid)?; - - let geo_hash = GeoHash::try_from_params(location.lat, location.lon) - .map_err(|_| >::InvalidLocationForGeohash)?; - Self::remove_location_intern(cid, location, geo_hash); - info!(target: LOG, "removed location {:?} to community with cid: {:?}", location, cid); - Self::deposit_event(Event::LocationRemoved(cid, location)); - Ok(().into()) + Self::do_remove_loaction(cid, location) } /// Update the metadata of the community with `cid`. @@ -249,21 +203,7 @@ pub mod pallet { community_metadata: CommunityMetadataType, ) -> DispatchResultWithPostInfo { T::CommunityMaster::ensure_origin(origin)?; - - Self::ensure_cid_exists(&cid)?; - community_metadata - .validate() - .map_err(|_| >::InvalidCommunityMetadata)?; - - >::insert(cid, &community_metadata); - - sp_io::offchain_index::set(&cid.encode(), &community_metadata.name.encode()); - sp_io::offchain_index::set(CACHE_DIRTY_KEY, &true.encode()); - - info!(target: LOG, "updated community metadata for cid: {:?}", cid); - Self::deposit_event(Event::MetadataUpdated(cid)); - - Ok(().into()) + Self::do_update_community_metadata(cid, community_metadata) } #[pallet::call_index(4)] @@ -274,16 +214,7 @@ pub mod pallet { demurrage: Demurrage, ) -> DispatchResultWithPostInfo { T::CommunityMaster::ensure_origin(origin)?; - - Self::ensure_cid_exists(&cid)?; - - >::set_demurrage(&cid, demurrage) - .map_err(|_| >::InvalidDemurrage)?; - - info!(target: LOG, " updated demurrage for cid: {:?}", cid); - Self::deposit_event(Event::DemurrageUpdated(cid, demurrage)); - - Ok(().into()) + Self::do_update_demurrage(cid, demurrage) } #[pallet::call_index(5)] @@ -472,6 +403,97 @@ pub mod pallet { } impl Pallet { + pub fn do_add_loaction( + cid: CommunityIdentifier, + location: Location, + ) -> DispatchResultWithPostInfo { + ensure!( + >::current_phase() == CeremonyPhaseType::Registering, + Error::::RegistrationPhaseRequired + ); + Self::ensure_cid_exists(&cid)?; + Self::validate_location(&location)?; + let geo_hash = GeoHash::try_from_params(location.lat, location.lon) + .map_err(|_| >::InvalidLocationForGeohash)?; + // insert location into locations + let mut locations = Self::locations(cid, &geo_hash); + match locations.binary_search(&location) { + Ok(_) => (), + Err(index) => { + locations + .try_insert(index, location) + .map_err(|_| Error::::TooManyLocationsPerGeohash)?; + >::insert(cid, &geo_hash, locations); + }, + } + // check if cid is in cids_by_geohash, if not, add it + let mut cids = Self::cids_by_geohash(&geo_hash); + match cids.binary_search(&cid) { + Ok(_) => (), + Err(index) => { + cids.try_insert(index, cid) + .map_err(|_| Error::::TooManyCommunityIdentifiersPerGeohash)?; + >::insert(&geo_hash, cids); + }, + } + sp_io::offchain_index::set(CACHE_DIRTY_KEY, &true.encode()); + + info!(target: LOG, "added location {:?} to community with cid: {:?}", location, cid); + Self::deposit_event(Event::LocationAdded(cid, location)); + Ok(().into()) + } + + pub fn do_remove_loaction( + cid: CommunityIdentifier, + location: Location, + ) -> DispatchResultWithPostInfo { + ensure!( + >::current_phase() == CeremonyPhaseType::Registering, + Error::::RegistrationPhaseRequired + ); + Self::ensure_cid_exists(&cid)?; + + let geo_hash = GeoHash::try_from_params(location.lat, location.lon) + .map_err(|_| >::InvalidLocationForGeohash)?; + Self::remove_location_intern(cid, location, geo_hash); + info!(target: LOG, "removed location {:?} to community with cid: {:?}", location, cid); + Self::deposit_event(Event::LocationRemoved(cid, location)); + Ok(().into()) + } + pub fn do_update_community_metadata( + cid: CommunityIdentifier, + community_metadata: CommunityMetadataType, + ) -> DispatchResultWithPostInfo { + Self::ensure_cid_exists(&cid)?; + community_metadata + .validate() + .map_err(|_| >::InvalidCommunityMetadata)?; + + >::insert(cid, &community_metadata); + + sp_io::offchain_index::set(&cid.encode(), &community_metadata.name.encode()); + sp_io::offchain_index::set(CACHE_DIRTY_KEY, &true.encode()); + + info!(target: LOG, "updated community metadata for cid: {:?}", cid); + Self::deposit_event(Event::MetadataUpdated(cid)); + + Ok(().into()) + } + pub fn do_update_demurrage( + cid: CommunityIdentifier, + demurrage: Demurrage, + ) -> DispatchResultWithPostInfo { + Self::ensure_cid_exists(&cid)?; + + >::set_demurrage(&cid, demurrage) + .map_err(|_| >::InvalidDemurrage)?; + + info!(target: LOG, " updated demurrage for cid: {:?}", cid); + Self::deposit_event(Event::DemurrageUpdated(cid, demurrage)); + + Ok(().into()) + } + pub fn do_update_nominal_income( cid: CommunityIdentifier, nominal_income: NominalIncomeType, diff --git a/democracy/src/lib.rs b/democracy/src/lib.rs index b669107c..df92465a 100644 --- a/democracy/src/lib.rs +++ b/democracy/src/lib.rs @@ -201,7 +201,7 @@ pub mod pallet { origin: OriginFor, proposal_action: ProposalAction, ) -> DispatchResultWithPostInfo { - if Self::enactment_queue(proposal_action.get_identifier()).is_some() { + if Self::enactment_queue(proposal_action.clone().get_identifier()).is_some() { return Err(Error::::ProposalWaitingForEnactment.into()) } let _sender = ensure_signed(origin)?; @@ -215,8 +215,8 @@ pub mod pallet { start: now, start_cindex: cindex, state: ProposalState::Ongoing, - action: proposal_action, - electorate_size: Self::get_electorate(cindex, proposal_action)?, + action: proposal_action.clone(), + electorate_size: Self::get_electorate(cindex, proposal_action.clone())?, }; let proposal_identifier = @@ -376,7 +376,7 @@ pub mod pallet { let mut approved = false; let old_proposal_state = proposal.state; let now = >::get(); - let proposal_action_identifier = proposal.action.get_identifier(); + let proposal_action_identifier = proposal.action.clone().get_identifier(); let cancelled_at = Self::cancelled_at(proposal_action_identifier); let proposal_cancelled = proposal.start < cancelled_at; let proposal_too_old = now - proposal.start > T::ProposalLifetime::get(); @@ -489,7 +489,22 @@ pub mod pallet { let mut proposal = Self::proposals(proposal_id).ok_or(Error::::InexistentProposal)?; - match proposal.action { + match proposal.action.clone() { + ProposalAction::AddLocation(cid, location) => { + >::do_add_loaction(cid, location)?; + }, + ProposalAction::RemoveLocation(cid, location) => { + >::do_remove_loaction(cid, location)?; + }, + ProposalAction::UpdateCommunityMetadata(cid, community_metadata) => { + >::do_update_community_metadata( + cid, + community_metadata, + )?; + }, + ProposalAction::UpdateDemurrage(cid, demurrage) => { + >::do_update_demurrage(cid, demurrage)?; + }, ProposalAction::UpdateNominalIncome(cid, nominal_income) => { >::do_update_nominal_income( cid, diff --git a/democracy/src/tests.rs b/democracy/src/tests.rs index 2ead9905..04114a56 100644 --- a/democracy/src/tests.rs +++ b/democracy/src/tests.rs @@ -17,10 +17,17 @@ //! Unit tests for the tokens module. use super::*; -use crate::mock::{EncointerCeremonies, EncointerCommunities, EncointerScheduler, Timestamp}; +use crate::mock::{ + EncointerBalances, EncointerCeremonies, EncointerCommunities, EncointerScheduler, Timestamp, +}; use encointer_primitives::{ + balances::Demurrage, ceremonies::{InactivityTimeoutType, Reputation}, - communities::{CommunityIdentifier, NominalIncome as NominalIncomeType}, + common::{FromStr, PalletString}, + communities::{ + CommunityIdentifier, CommunityMetadata as CommunityMetadataType, Degree, GeoHash, Location, + NominalIncome as NominalIncomeType, + }, democracy::{ProposalAction, ProposalActionIdentifier, ProposalState, Tally, Vote}, }; use frame_support::{ @@ -29,7 +36,7 @@ use frame_support::{ }; use mock::{new_test_ext, EncointerDemocracy, RuntimeOrigin, System, TestRuntime}; use sp_runtime::BoundedVec; -use std::str::FromStr; +use std::str::FromStr as StdFromStr; use test_utils::{ helpers::{ account_id, add_population, event_at_index, get_num_events, last_event, @@ -123,7 +130,7 @@ fn proposal_submission_fails_if_proposal_in_enactment_queue() { let proposal_action = ProposalAction::UpdateNominalIncome(cid, NominalIncomeType::from(100u32)); - EnactmentQueue::::insert(proposal_action.get_identifier(), 100); + EnactmentQueue::::insert(proposal_action.clone().get_identifier(), 100); assert_err!( EncointerDemocracy::submit_proposal( @@ -499,7 +506,7 @@ fn do_update_proposal_state_works() { assert_ok!(EncointerDemocracy::submit_proposal( RuntimeOrigin::signed(alice), - proposal_action + proposal_action.clone() )); assert_ok!(EncointerDemocracy::do_update_proposal_state(1)); @@ -533,13 +540,16 @@ fn do_update_proposal_state_works() { ProposalState::Confirming { since: 0 } ); - assert_eq!(EncointerDemocracy::enactment_queue(proposal_action.get_identifier()), None); + assert_eq!( + EncointerDemocracy::enactment_queue(proposal_action.clone().get_identifier()), + None + ); advance_n_blocks(11); // proposal is enacted assert_eq!(EncointerDemocracy::do_update_proposal_state(1).unwrap(), true); assert_eq!(EncointerDemocracy::proposals(1).unwrap().state, ProposalState::Approved); assert_eq!( - EncointerDemocracy::enactment_queue(proposal_action.get_identifier()).unwrap(), + EncointerDemocracy::enactment_queue(proposal_action.clone().get_identifier()).unwrap(), 1 ); }); @@ -590,14 +600,14 @@ fn test_get_electorate_works() { let proposal_action = ProposalAction::SetInactivityTimeout(8); assert_ok!(EncointerDemocracy::submit_proposal( RuntimeOrigin::signed(alice.clone()), - proposal_action + proposal_action.clone() )); let proposal_action2 = ProposalAction::UpdateNominalIncome(cid, NominalIncomeType::from(100u32)); assert_ok!(EncointerDemocracy::submit_proposal( RuntimeOrigin::signed(alice.clone()), - proposal_action + proposal_action.clone() )); assert_eq!(EncointerDemocracy::get_electorate(7, proposal_action).unwrap(), 5); @@ -658,18 +668,18 @@ fn enactment_updates_proposal_metadata_and_enactment_queue() { let proposal_action = ProposalAction::SetInactivityTimeout(8); assert_ok!(EncointerDemocracy::submit_proposal( RuntimeOrigin::signed(alice.clone()), - proposal_action + proposal_action.clone() )); let proposal_action2 = ProposalAction::UpdateNominalIncome(cid, NominalIncomeType::from(100u32)); assert_ok!(EncointerDemocracy::submit_proposal( RuntimeOrigin::signed(alice.clone()), - proposal_action2 + proposal_action2.clone() )); - EnactmentQueue::::insert(proposal_action.get_identifier(), 1); - EnactmentQueue::::insert(proposal_action2.get_identifier(), 2); + EnactmentQueue::::insert(proposal_action.clone().get_identifier(), 1); + EnactmentQueue::::insert(proposal_action2.clone().get_identifier(), 2); run_to_next_phase(); run_to_next_phase(); @@ -706,7 +716,13 @@ fn proposal_happy_flow() { )); assert_eq!( last_event::(), - Some(Event::ProposalSubmitted { proposal_id: 1, proposal_action }.into()) + Some( + Event::ProposalSubmitted { + proposal_id: 1, + proposal_action: proposal_action.clone() + } + .into() + ) ); assert_ok!(EncointerDemocracy::vote( @@ -755,11 +771,126 @@ fn proposal_happy_flow() { ); assert_eq!(EncointerDemocracy::proposals(1).unwrap().state, ProposalState::Enacted); - assert_eq!(EncointerDemocracy::enactment_queue(proposal_action.get_identifier()), None); + assert_eq!( + EncointerDemocracy::enactment_queue(proposal_action.clone().get_identifier()), + None + ); assert_eq!(EncointerCommunities::nominal_income(cid), NominalIncomeType::from(13037u32)); }); } +#[test] +fn enact_add_location_works() { + new_test_ext().execute_with(|| { + let cid = create_cid(); + let alice = alice(); + let location = Location { lat: Degree::from_num(10.0), lon: Degree::from_num(10.0) }; + let proposal_action = ProposalAction::AddLocation(cid, location); + assert_ok!(EncointerDemocracy::submit_proposal( + RuntimeOrigin::signed(alice.clone()), + proposal_action.clone() + )); + + let geo_hash = GeoHash::try_from_params(location.lat, location.lon).unwrap(); + EnactmentQueue::::insert(proposal_action.clone().get_identifier(), 1); + assert_eq!(EncointerCommunities::locations(cid, geo_hash.clone()).len(), 0); + + run_to_next_phase(); + run_to_next_phase(); + run_to_next_phase(); + + assert_eq!(EncointerDemocracy::proposals(1).unwrap().state, ProposalState::Enacted); + assert_eq!(EncointerDemocracy::enactment_queue(proposal_action.get_identifier()), None); + assert_eq!(EncointerCommunities::locations(cid, geo_hash).len(), 1); + }); +} + +#[test] +fn enact_remove_location_works() { + new_test_ext().execute_with(|| { + let cid = create_cid(); + let alice = alice(); + let location = Location { lat: Degree::from_num(10.0), lon: Degree::from_num(10.0) }; + let _ = EncointerCommunities::do_add_loaction(cid, location); + let proposal_action = ProposalAction::RemoveLocation(cid, location); + assert_ok!(EncointerDemocracy::submit_proposal( + RuntimeOrigin::signed(alice.clone()), + proposal_action.clone() + )); + + let geo_hash = GeoHash::try_from_params(location.lat, location.lon).unwrap(); + EnactmentQueue::::insert(proposal_action.clone().get_identifier(), 1); + assert_eq!(EncointerCommunities::locations(cid, geo_hash.clone()).len(), 1); + + run_to_next_phase(); + run_to_next_phase(); + run_to_next_phase(); + + assert_eq!(EncointerDemocracy::proposals(1).unwrap().state, ProposalState::Enacted); + assert_eq!(EncointerDemocracy::enactment_queue(proposal_action.get_identifier()), None); + assert_eq!(EncointerCommunities::locations(cid, geo_hash).len(), 0); + }); +} + +#[test] +fn enact_update_community_metadata_works() { + new_test_ext().execute_with(|| { + let cid = create_cid(); + let alice = alice(); + let community_metadata: CommunityMetadataType = CommunityMetadataType { + name: PalletString::from_str("abc1337").unwrap(), + symbol: PalletString::from_str("DEF").unwrap(), + ..Default::default() + }; + + let proposal_action = ProposalAction::UpdateCommunityMetadata(cid, community_metadata); + assert_ok!(EncointerDemocracy::submit_proposal( + RuntimeOrigin::signed(alice.clone()), + proposal_action.clone() + )); + + EnactmentQueue::::insert(proposal_action.clone().get_identifier(), 1); + + run_to_next_phase(); + run_to_next_phase(); + run_to_next_phase(); + + assert_eq!(EncointerDemocracy::proposals(1).unwrap().state, ProposalState::Enacted); + assert_eq!(EncointerDemocracy::enactment_queue(proposal_action.get_identifier()), None); + assert_eq!( + EncointerCommunities::community_metadata(cid).name, + PalletString::from_str("abc1337").unwrap() + ); + }); +} + +#[test] +fn enact_update_demurrage_works() { + new_test_ext().execute_with(|| { + let cid = create_cid(); + let alice = alice(); + let demurrage = Demurrage::from_num(0.0001337); + + let proposal_action = ProposalAction::UpdateDemurrage(cid, demurrage); + assert_ok!(EncointerDemocracy::submit_proposal( + RuntimeOrigin::signed(alice.clone()), + proposal_action.clone() + )); + + EnactmentQueue::::insert(proposal_action.clone().get_identifier(), 1); + + assert!(EncointerBalances::demurrage_per_block(&cid) != demurrage); + + run_to_next_phase(); + run_to_next_phase(); + run_to_next_phase(); + + assert_eq!(EncointerDemocracy::proposals(1).unwrap().state, ProposalState::Enacted); + assert_eq!(EncointerDemocracy::enactment_queue(proposal_action.get_identifier()), None); + assert_eq!(EncointerBalances::demurrage_per_block(&cid), demurrage); + }); +} + #[test] fn enact_update_nominal_income_works() { new_test_ext().execute_with(|| { @@ -769,10 +900,10 @@ fn enact_update_nominal_income_works() { ProposalAction::UpdateNominalIncome(cid, NominalIncomeType::from(13037u32)); assert_ok!(EncointerDemocracy::submit_proposal( RuntimeOrigin::signed(alice.clone()), - proposal_action + proposal_action.clone() )); - EnactmentQueue::::insert(proposal_action.get_identifier(), 1); + EnactmentQueue::::insert(proposal_action.clone().get_identifier(), 1); run_to_next_phase(); run_to_next_phase(); @@ -792,17 +923,20 @@ fn enact_set_inactivity_timeout_works() { ProposalAction::SetInactivityTimeout(InactivityTimeoutType::from(13037u32)); assert_ok!(EncointerDemocracy::submit_proposal( RuntimeOrigin::signed(alice.clone()), - proposal_action + proposal_action.clone() )); - EnactmentQueue::::insert(proposal_action.get_identifier(), 1); + EnactmentQueue::::insert(proposal_action.clone().get_identifier(), 1); run_to_next_phase(); run_to_next_phase(); run_to_next_phase(); assert_eq!(EncointerDemocracy::proposals(1).unwrap().state, ProposalState::Enacted); - assert_eq!(EncointerDemocracy::enactment_queue(proposal_action.get_identifier()), None); + assert_eq!( + EncointerDemocracy::enactment_queue(proposal_action.clone().get_identifier()), + None + ); assert_eq!( EncointerCeremonies::inactivity_timeout(), InactivityTimeoutType::from(13037u32) @@ -820,10 +954,10 @@ fn enactment_error_fires_event() { ProposalAction::UpdateNominalIncome(cid, NominalIncomeType::from(13037u32)); assert_ok!(EncointerDemocracy::submit_proposal( RuntimeOrigin::signed(alice.clone()), - proposal_action + proposal_action.clone() )); - EnactmentQueue::::insert(proposal_action.get_identifier(), 1); + EnactmentQueue::::insert(proposal_action.clone().get_identifier(), 1); run_to_next_phase(); run_to_next_phase(); diff --git a/primitives/src/democracy.rs b/primitives/src/democracy.rs index 80707de2..101233c9 100644 --- a/primitives/src/democracy.rs +++ b/primitives/src/democracy.rs @@ -1,6 +1,10 @@ use crate::{ + balances::Demurrage, ceremonies::{CommunityCeremony, InactivityTimeoutType}, - communities::{CommunityIdentifier, NominalIncome as NominalIncomeType}, + communities::{ + CommunityIdentifier, CommunityMetadata as CommunityMetadataType, Location, + NominalIncome as NominalIncomeType, + }, }; use codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; @@ -41,10 +45,14 @@ pub enum ProposalAccessPolicy { Community(CommunityIdentifier), } -#[derive(Encode, Decode, RuntimeDebug, Clone, Copy, PartialEq, Eq, TypeInfo, MaxEncodedLen)] +#[derive(Encode, Decode, RuntimeDebug, Clone, PartialEq, Eq, TypeInfo, MaxEncodedLen)] #[cfg_attr(feature = "serde_derive", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde_derive", serde(rename_all = "camelCase"))] pub enum ProposalAction { + AddLocation(CommunityIdentifier, Location), + RemoveLocation(CommunityIdentifier, Location), + UpdateCommunityMetadata(CommunityIdentifier, CommunityMetadataType), + UpdateDemurrage(CommunityIdentifier, Demurrage), UpdateNominalIncome(CommunityIdentifier, NominalIncomeType), SetInactivityTimeout(InactivityTimeoutType), } @@ -53,6 +61,10 @@ pub enum ProposalAction { #[cfg_attr(feature = "serde_derive", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde_derive", serde(rename_all = "camelCase"))] pub enum ProposalActionIdentifier { + AddLocation(CommunityIdentifier), + RemoveLocation(CommunityIdentifier), + UpdateCommunityMetadata(CommunityIdentifier), + UpdateDemurrage(CommunityIdentifier), UpdateNominalIncome(CommunityIdentifier), SetInactivityTimeout, } @@ -60,6 +72,10 @@ pub enum ProposalActionIdentifier { impl ProposalAction { pub fn get_access_policy(self) -> ProposalAccessPolicy { match self { + ProposalAction::AddLocation(cid, _) => ProposalAccessPolicy::Community(cid), + ProposalAction::RemoveLocation(cid, _) => ProposalAccessPolicy::Community(cid), + ProposalAction::UpdateCommunityMetadata(cid, _) => ProposalAccessPolicy::Community(cid), + ProposalAction::UpdateDemurrage(cid, _) => ProposalAccessPolicy::Community(cid), ProposalAction::UpdateNominalIncome(cid, _) => ProposalAccessPolicy::Community(cid), ProposalAction::SetInactivityTimeout(_) => ProposalAccessPolicy::Global, } @@ -67,6 +83,12 @@ impl ProposalAction { pub fn get_identifier(self) -> ProposalActionIdentifier { match self { + ProposalAction::AddLocation(cid, _) => ProposalActionIdentifier::AddLocation(cid), + ProposalAction::RemoveLocation(cid, _) => ProposalActionIdentifier::RemoveLocation(cid), + ProposalAction::UpdateCommunityMetadata(cid, _) => + ProposalActionIdentifier::UpdateCommunityMetadata(cid), + ProposalAction::UpdateDemurrage(cid, _) => + ProposalActionIdentifier::UpdateDemurrage(cid), ProposalAction::UpdateNominalIncome(cid, _) => ProposalActionIdentifier::UpdateNominalIncome(cid), ProposalAction::SetInactivityTimeout(_) => From 8e04580dc8e12975b92240a621ab97692b6b0e88 Mon Sep 17 00:00:00 2001 From: pifragile Date: Wed, 20 Dec 2023 15:59:41 +0800 Subject: [PATCH 07/15] taplo fmt --- democracy/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/democracy/Cargo.toml b/democracy/Cargo.toml index c6f17c31..4a55cb37 100644 --- a/democracy/Cargo.toml +++ b/democracy/Cargo.toml @@ -18,8 +18,8 @@ scale-info = { version = "2.9.0", default-features = false } encointer-ceremonies = { package = "pallet-encointer-ceremonies", path = "../ceremonies", default-features = false, version = "2.0.0" } encointer-communities = { package = "pallet-encointer-communities", path = "../communities", default-features = false, version = "2.0.0" } encointer-primitives = { path = "../primitives", default-features = false, version = "2.0.0" } -encointer-scheduler = { package = "pallet-encointer-scheduler", path = "../scheduler", default-features = false, version = "2.0.0" } encointer-reputation-commitments = { package = "pallet-encointer-reputation-commitments", path = "../reputation-commitments", default-features = false, version = "2.0.0" } +encointer-scheduler = { package = "pallet-encointer-scheduler", path = "../scheduler", default-features = false, version = "2.0.0" } # substrate deps frame-support = { default-features = false, version = "24.0.0" } From 099b12a0d7beca0910a6148cd32acc89ce0c52ac Mon Sep 17 00:00:00 2001 From: pifragile Date: Wed, 20 Dec 2023 21:01:20 +0800 Subject: [PATCH 08/15] remove VoteEntries and fix benchmarks --- democracy/src/benchmarking.rs | 45 +++++++++++++++++++++-------------- democracy/src/lib.rs | 11 --------- 2 files changed, 27 insertions(+), 29 deletions(-) diff --git a/democracy/src/benchmarking.rs b/democracy/src/benchmarking.rs index f023e3eb..a1dae2d6 100644 --- a/democracy/src/benchmarking.rs +++ b/democracy/src/benchmarking.rs @@ -7,18 +7,22 @@ use encointer_primitives::{ storage::{current_ceremony_index_key, global_reputation_count, participant_reputation}, }; use frame_benchmarking::{account, benchmarks, impl_benchmark_test_suite}; -use frame_support::{assert_ok, traits::OnInitialize, BoundedVec}; +use frame_support::{ + assert_ok, + traits::{OnFinalize, OriginTrait}, + BoundedVec, +}; use frame_system::RawOrigin; #[cfg(not(feature = "std"))] use sp_std::vec; -fn advance_n_blocks(n: u64) { - for _ in 0..n { - frame_system::Pallet::::set_block_number( - frame_system::Pallet::::block_number() + 1u32.into(), - ); - frame_system::Pallet::::on_initialize(frame_system::Pallet::::block_number()); - } +fn advance_timestamp_equivalent_to_n_blocks(n: u64) { + let offset: T::Moment = (n * 6000u64) + .try_into() + .unwrap_or_else(|_| panic!("Something went horribly wrong!")); + let new_time: T::Moment = pallet_timestamp::Pallet::::get() + offset; + let _ = pallet_timestamp::Pallet::::set(T::RuntimeOrigin::none(), new_time); + pallet_timestamp::Pallet::::on_finalize(frame_system::Pallet::::block_number()); } benchmarks! { @@ -38,28 +42,32 @@ benchmarks! { let zoran = account::("zoran", 1, 1); let cid = CommunityIdentifier::default(); - let proposal_action = ProposalAction::SetInactivityTimeout(8); - assert_ok!(EncointerDemocracy::::submit_proposal( - RawOrigin::Signed(zoran.clone()).into(), - proposal_action - )); - frame_support::storage::unhashed::put_raw(&participant_reputation((cid, 3), &zoran), &Reputation::VerifiedUnlinked.encode()); frame_support::storage::unhashed::put_raw(&participant_reputation((cid, 4), &zoran), &Reputation::VerifiedUnlinked.encode()); frame_support::storage::unhashed::put_raw(&participant_reputation((cid, 5), &zoran), &Reputation::VerifiedUnlinked.encode()); + frame_support::storage::unhashed::put_raw(&global_reputation_count(3), &1u128.encode()); + frame_support::storage::unhashed::put_raw(&global_reputation_count(4), &1u128.encode()); + frame_support::storage::unhashed::put_raw(&global_reputation_count(5), &1u128.encode()); + let reputation_vec: ReputationVecOf = BoundedVec::try_from(vec![ (cid, 3), (cid, 4), (cid, 5), ]).unwrap(); - assert!(>::iter().next().is_none()); + let proposal_action = ProposalAction::SetInactivityTimeout(8); + assert_ok!(EncointerDemocracy::::submit_proposal( + RawOrigin::Signed(zoran.clone()).into(), + proposal_action + )); + + assert_eq!(>::get(1).unwrap().ayes, 0); }: _(RawOrigin::Signed(zoran.clone()), 1, Vote::Aye, reputation_vec) verify { - assert!(>::iter().next().is_some()); + assert_eq!(>::get(1).unwrap().ayes, 3); } update_proposal_state { @@ -67,6 +75,8 @@ benchmarks! { let zoran = account::("zoran", 1, 1); let cid = CommunityIdentifier::default(); + frame_support::storage::unhashed::put_raw(&global_reputation_count(5), &3u128.encode()); + let proposal_action = ProposalAction::SetInactivityTimeout(8); assert_ok!(EncointerDemocracy::::submit_proposal( RawOrigin::Signed(zoran.clone()).into(), @@ -76,11 +86,10 @@ benchmarks! { Tallies::::insert(1, Tally { turnout: 3, ayes: 3 }); - frame_support::storage::unhashed::put_raw(&global_reputation_count(5), &3u128.encode()); assert_eq!(EncointerDemocracy::::proposals(1).unwrap().state, ProposalState::Ongoing); EncointerDemocracy::::update_proposal_state(RawOrigin::Signed(zoran.clone()).into(), 1).ok(); assert!(>::iter().next().is_none()); - advance_n_blocks::(21); + advance_timestamp_equivalent_to_n_blocks::(21); }: _(RawOrigin::Signed(zoran), 1) verify { diff --git a/democracy/src/lib.rs b/democracy/src/lib.rs index df92465a..adf664f1 100644 --- a/democracy/src/lib.rs +++ b/democracy/src/lib.rs @@ -156,17 +156,6 @@ pub mod pallet { pub(super) type Tallies = StorageMap<_, Blake2_128Concat, ProposalIdType, Tally, OptionQuery>; - #[pallet::storage] - #[pallet::getter(fn vote_entries)] - pub(super) type VoteEntries = StorageDoubleMap< - _, - Blake2_128Concat, - ProposalIdType, - Blake2_128Concat, - VoteEntry, - (), - ValueQuery, - >; // TODO set default value #[pallet::storage] #[pallet::getter(fn cancelled_at)] From a74b553659d041640376b5bce3ce9c78f24d9b28 Mon Sep 17 00:00:00 2001 From: pifragile Date: Mon, 26 Feb 2024 19:43:35 +0800 Subject: [PATCH 09/15] fix toml --- democracy/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/democracy/Cargo.toml b/democracy/Cargo.toml index 30d27a1e..729be6e8 100644 --- a/democracy/Cargo.toml +++ b/democracy/Cargo.toml @@ -18,7 +18,7 @@ scale-info = { workspace = true } encointer-primitives = { workspace = true } pallet-encointer-ceremonies = { workspace = true } pallet-encointer-communities = { workspace = true } -encointer-reputation-commitments = {workspace = true} +pallet-encointer-reputation-commitments = {workspace = true} pallet-encointer-scheduler = { workspace = true } # substrate deps From 8e0265955c73b314c7c50527856b03f8cb4ac996 Mon Sep 17 00:00:00 2001 From: pifragile Date: Mon, 26 Feb 2024 19:44:33 +0800 Subject: [PATCH 10/15] fix typo --- communities/src/lib.rs | 4 ++-- democracy/src/lib.rs | 2 +- democracy/src/tests.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/communities/src/lib.rs b/communities/src/lib.rs index b7c3ccbd..df0b8f85 100644 --- a/communities/src/lib.rs +++ b/communities/src/lib.rs @@ -175,7 +175,7 @@ pub mod pallet { location: Location, ) -> DispatchResultWithPostInfo { T::TrustableForNonDestructiveAction::ensure_origin(origin)?; - Self::do_add_loaction(cid, location) + Self::do_add_location(cid, location) } /// Remove an existing meetup `location` from the community with `cid`. @@ -405,7 +405,7 @@ pub mod pallet { } impl Pallet { - pub fn do_add_loaction( + pub fn do_add_location( cid: CommunityIdentifier, location: Location, ) -> DispatchResultWithPostInfo { diff --git a/democracy/src/lib.rs b/democracy/src/lib.rs index 2158ac42..e8f3740c 100644 --- a/democracy/src/lib.rs +++ b/democracy/src/lib.rs @@ -482,7 +482,7 @@ pub mod pallet { match proposal.action.clone() { ProposalAction::AddLocation(cid, location) => { - >::do_add_loaction(cid, location)?; + >::do_add_location(cid, location)?; }, ProposalAction::RemoveLocation(cid, location) => { >::do_remove_loaction(cid, location)?; diff --git a/democracy/src/tests.rs b/democracy/src/tests.rs index cd1c499d..c3c55325 100644 --- a/democracy/src/tests.rs +++ b/democracy/src/tests.rs @@ -811,7 +811,7 @@ fn enact_remove_location_works() { let cid = create_cid(); let alice = alice(); let location = Location { lat: Degree::from_num(10.0), lon: Degree::from_num(10.0) }; - let _ = EncointerCommunities::do_add_loaction(cid, location); + let _ = EncointerCommunities::do_add_location(cid, location); let proposal_action = ProposalAction::RemoveLocation(cid, location); assert_ok!(EncointerDemocracy::submit_proposal( RuntimeOrigin::signed(alice.clone()), From ff6288bbfb2dedc5d38af16dbfcdc06ad9b50a96 Mon Sep 17 00:00:00 2001 From: pifragile Date: Mon, 26 Feb 2024 19:46:33 +0800 Subject: [PATCH 11/15] taplo fmt --- democracy/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/democracy/Cargo.toml b/democracy/Cargo.toml index 729be6e8..7090298f 100644 --- a/democracy/Cargo.toml +++ b/democracy/Cargo.toml @@ -18,7 +18,7 @@ scale-info = { workspace = true } encointer-primitives = { workspace = true } pallet-encointer-ceremonies = { workspace = true } pallet-encointer-communities = { workspace = true } -pallet-encointer-reputation-commitments = {workspace = true} +pallet-encointer-reputation-commitments = { workspace = true } pallet-encointer-scheduler = { workspace = true } # substrate deps From 1a97fa9f561a6298ed2a5b01f6d7960dcc139d0a Mon Sep 17 00:00:00 2001 From: pifragile Date: Mon, 26 Feb 2024 19:48:03 +0800 Subject: [PATCH 12/15] fmt --- communities/src/lib.rs | 6 ++++-- primitives/src/democracy.rs | 20 ++++++++++++-------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/communities/src/lib.rs b/communities/src/lib.rs index df0b8f85..2e21bce8 100644 --- a/communities/src/lib.rs +++ b/communities/src/lib.rs @@ -410,7 +410,8 @@ impl Pallet { location: Location, ) -> DispatchResultWithPostInfo { ensure!( - >::current_phase() == CeremonyPhaseType::Registering, + >::current_phase() + == CeremonyPhaseType::Registering, Error::::RegistrationPhaseRequired ); Self::ensure_cid_exists(&cid)?; @@ -450,7 +451,8 @@ impl Pallet { location: Location, ) -> DispatchResultWithPostInfo { ensure!( - >::current_phase() == CeremonyPhaseType::Registering, + >::current_phase() + == CeremonyPhaseType::Registering, Error::::RegistrationPhaseRequired ); Self::ensure_cid_exists(&cid)?; diff --git a/primitives/src/democracy.rs b/primitives/src/democracy.rs index ac3bf7fe..ae83911f 100644 --- a/primitives/src/democracy.rs +++ b/primitives/src/democracy.rs @@ -85,14 +85,18 @@ impl ProposalAction { match self { ProposalAction::AddLocation(cid, _) => ProposalActionIdentifier::AddLocation(cid), ProposalAction::RemoveLocation(cid, _) => ProposalActionIdentifier::RemoveLocation(cid), - ProposalAction::UpdateCommunityMetadata(cid, _) => - ProposalActionIdentifier::UpdateCommunityMetadata(cid), - ProposalAction::UpdateDemurrage(cid, _) => - ProposalActionIdentifier::UpdateDemurrage(cid), - ProposalAction::UpdateNominalIncome(cid, _) => - ProposalActionIdentifier::UpdateNominalIncome(cid), - ProposalAction::SetInactivityTimeout(_) => - ProposalActionIdentifier::SetInactivityTimeout, + ProposalAction::UpdateCommunityMetadata(cid, _) => { + ProposalActionIdentifier::UpdateCommunityMetadata(cid) + }, + ProposalAction::UpdateDemurrage(cid, _) => { + ProposalActionIdentifier::UpdateDemurrage(cid) + }, + ProposalAction::UpdateNominalIncome(cid, _) => { + ProposalActionIdentifier::UpdateNominalIncome(cid) + }, + ProposalAction::SetInactivityTimeout(_) => { + ProposalActionIdentifier::SetInactivityTimeout + }, } } } From 17ba8afbf0a32e93ff57ec0b71c66bc11e17716c Mon Sep 17 00:00:00 2001 From: pifragile Date: Mon, 26 Feb 2024 20:00:00 +0800 Subject: [PATCH 13/15] fixes --- democracy/src/lib.rs | 34 ++++++++++++++++++---------------- democracy/src/mock.rs | 2 +- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/democracy/src/lib.rs b/democracy/src/lib.rs index e8f3740c..e9d3590f 100644 --- a/democracy/src/lib.rs +++ b/democracy/src/lib.rs @@ -28,7 +28,6 @@ use encointer_primitives::{ use frame_support::dispatch::DispatchErrorWithPostInfo; -use pallet_encointer_scheduler::OnCeremonyPhaseChange; use frame_support::{ sp_runtime::{ traits::{CheckedAdd, CheckedDiv, CheckedSub}, @@ -36,6 +35,7 @@ use frame_support::{ }, traits::Get, }; +use pallet_encointer_scheduler::OnCeremonyPhaseChange; pub use weights::WeightInfo; @@ -158,11 +158,10 @@ pub mod pallet { pub(super) type Tallies = StorageMap<_, Blake2_128Concat, ProposalIdType, Tally, OptionQuery>; - // TODO set default value #[pallet::storage] #[pallet::getter(fn cancelled_at)] pub(super) type CancelledAt = - StorageMap<_, Blake2_128Concat, ProposalActionIdentifier, T::Moment, ValueQuery>; + StorageMap<_, Blake2_128Concat, ProposalActionIdentifier, T::Moment, OptionQuery>; #[pallet::storage] #[pallet::getter(fn enactment_queue)] @@ -213,10 +212,11 @@ pub mod pallet { let proposal_identifier = ["democracyProposal".as_bytes(), next_proposal_id.to_string().as_bytes()].concat(); - let purpose_id = >::do_register_purpose( - DescriptorType::try_from(proposal_identifier) - .map_err(|_| >::PurposeIdCreationFailed)?, - )?; + let purpose_id = + >::do_register_purpose( + DescriptorType::try_from(proposal_identifier) + .map_err(|_| >::PurposeIdCreationFailed)?, + )?; >::insert(next_proposal_id, proposal); >::insert(next_proposal_id, purpose_id); @@ -289,7 +289,8 @@ pub mod pallet { fn relevant_cindexes( start_cindex: CeremonyIndexType, ) -> Result, Error> { - let reputation_lifetime = >::reputation_lifetime(); + let reputation_lifetime = + >::reputation_lifetime(); let cycle_duration = >::get_cycle_duration(); let proposal_lifetime = T::ProposalLifetime::get(); // ceil(proposal_lifetime / cycle_duration) @@ -340,7 +341,7 @@ pub mod pallet { } } - if >::do_commit_reputation( + if >::do_commit_reputation( account_id, community_ceremony.0, community_ceremony.1, @@ -349,7 +350,7 @@ pub mod pallet { ) .is_err() { - continue + continue; } eligible_reputation_count += 1; @@ -369,7 +370,8 @@ pub mod pallet { let now = >::get(); let proposal_action_identifier = proposal.action.clone().get_identifier(); let cancelled_at = Self::cancelled_at(proposal_action_identifier); - let proposal_cancelled = proposal.start < cancelled_at; + let proposal_cancelled = + cancelled_at.is_some() && proposal.start < cancelled_at.unwrap(); let proposal_too_old = now - proposal.start > T::ProposalLifetime::get(); if proposal_cancelled || proposal_too_old { proposal.state = ProposalState::Cancelled; @@ -482,19 +484,19 @@ pub mod pallet { match proposal.action.clone() { ProposalAction::AddLocation(cid, location) => { - >::do_add_location(cid, location)?; + >::do_add_location(cid, location)?; }, ProposalAction::RemoveLocation(cid, location) => { - >::do_remove_loaction(cid, location)?; + >::do_remove_loaction(cid, location)?; }, ProposalAction::UpdateCommunityMetadata(cid, community_metadata) => { - >::do_update_community_metadata( + >::do_update_community_metadata( cid, community_metadata, )?; }, ProposalAction::UpdateDemurrage(cid, demurrage) => { - >::do_update_demurrage(cid, demurrage)?; + >::do_update_demurrage(cid, demurrage)?; }, ProposalAction::UpdateNominalIncome(cid, nominal_income) => { >::do_update_nominal_income( @@ -504,7 +506,7 @@ pub mod pallet { }, ProposalAction::SetInactivityTimeout(inactivity_timeout) => { - >::do_set_inactivity_timeout( + >::do_set_inactivity_timeout( inactivity_timeout, )?; }, diff --git a/democracy/src/mock.rs b/democracy/src/mock.rs index 4b430d3d..20f2265c 100644 --- a/democracy/src/mock.rs +++ b/democracy/src/mock.rs @@ -34,7 +34,7 @@ frame_support::construct_runtime!( EncointerCeremonies: pallet_encointer_ceremonies::{Pallet, Call, Storage, Event}, EncointerBalances: pallet_encointer_balances::{Pallet, Call, Storage, Event}, EncointerDemocracy: dut::{Pallet, Call, Storage, Config, Event}, - EncointerReputationCommitments:encointer_reputation_commitments::{Pallet, Call, Storage, Event}, + EncointerReputationCommitments:pallet_encointer_reputation_commitments::{Pallet, Call, Storage, Event}, } ); From 60094b3c9093be1405845ec7b0c63507071b8767 Mon Sep 17 00:00:00 2001 From: pifragile Date: Mon, 1 Jan 2024 16:24:58 +0800 Subject: [PATCH 14/15] add no_std to_string --- democracy/src/lib.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/democracy/src/lib.rs b/democracy/src/lib.rs index e9d3590f..25201692 100644 --- a/democracy/src/lib.rs +++ b/democracy/src/lib.rs @@ -42,6 +42,14 @@ pub use weights::WeightInfo; #[cfg(not(feature = "std"))] use sp_std::vec::Vec; +#[cfg(not(feature = "std"))] +extern crate alloc; + +#[cfg(not(feature = "std"))] +use alloc::string::String; +#[cfg(not(feature = "std"))] +use alloc::string::ToString; + // Logger target //const LOG: &str = "encointer"; From cd08431b7c7b070cd1c9a053b9875531f0ecc7c7 Mon Sep 17 00:00:00 2001 From: Alain Brenzikofer Date: Mon, 26 Feb 2024 13:13:05 +0100 Subject: [PATCH 15/15] fix more typos --- communities/src/lib.rs | 4 ++-- democracy/src/lib.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/communities/src/lib.rs b/communities/src/lib.rs index 2e21bce8..cd8dbaa0 100644 --- a/communities/src/lib.rs +++ b/communities/src/lib.rs @@ -191,7 +191,7 @@ pub mod pallet { location: Location, ) -> DispatchResultWithPostInfo { T::CommunityMaster::ensure_origin(origin)?; - Self::do_remove_loaction(cid, location) + Self::do_remove_location(cid, location) } /// Update the metadata of the community with `cid`. @@ -446,7 +446,7 @@ impl Pallet { Ok(().into()) } - pub fn do_remove_loaction( + pub fn do_remove_location( cid: CommunityIdentifier, location: Location, ) -> DispatchResultWithPostInfo { diff --git a/democracy/src/lib.rs b/democracy/src/lib.rs index 25201692..ebc1b102 100644 --- a/democracy/src/lib.rs +++ b/democracy/src/lib.rs @@ -495,7 +495,7 @@ pub mod pallet { >::do_add_location(cid, location)?; }, ProposalAction::RemoveLocation(cid, location) => { - >::do_remove_loaction(cid, location)?; + >::do_remove_location(cid, location)?; }, ProposalAction::UpdateCommunityMetadata(cid, community_metadata) => { >::do_update_community_metadata(