Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: democracy - allow TC to remove a vote #866

Merged
merged 7 commits into from
Jul 18, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pallets/democracy/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "pallet-democracy"
version = "4.2.1"
version = "4.3.0"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2021"
license = "Apache-2.0"
Expand Down
50 changes: 45 additions & 5 deletions pallets/democracy/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,9 @@ pub mod pallet {
/// Hooks are actions that are executed on certain events.
/// Eg: on_vote
type DemocracyHooks: DemocracyHooks<Self::AccountId, BalanceOf<Self>>;

/// Origin for anyone able to force remove a vote.
type VoteRemovalOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = Self::AccountId>;
}

/// The number of (public) proposals that have been made so far.
Expand Down Expand Up @@ -988,7 +991,7 @@ pub mod pallet {
#[pallet::weight(T::WeightInfo::remove_vote(T::MaxVotes::get()))]
pub fn remove_vote(origin: OriginFor<T>, index: ReferendumIndex) -> DispatchResult {
let who = ensure_signed(origin)?;
Self::try_remove_vote(&who, index, UnvoteScope::Any)
Self::try_remove_vote(&who, index, UnvoteScope::Any, false)
}

/// Remove a vote for a referendum.
Expand Down Expand Up @@ -1020,7 +1023,7 @@ pub mod pallet {
} else {
UnvoteScope::OnlyExpired
};
Self::try_remove_vote(&target, index, scope)?;
Self::try_remove_vote(&target, index, scope, false)?;
Ok(())
}

Expand Down Expand Up @@ -1165,6 +1168,38 @@ pub mod pallet {
}
Ok(())
}

/// Allow to force remove a vote for a referendum.
///
/// The dispatch origin of this call must be `VoteRemovalOrigin`.
///
/// Only allowed if the referendum is finished.
///
/// The dispatch origin of this call must be _Signed_.
///
/// - `target`: The account of the vote to be removed; this account must have voted for
/// referendum `index`.
/// - `index`: The index of referendum of the vote to be removed.
///
/// Weight: `O(R + log R)` where R is the number of referenda that `target` has voted on.
/// Weight is calculated for the maximum number of vote.
#[pallet::call_index(19)]
#[pallet::weight(T::WeightInfo::remove_other_vote(T::MaxVotes::get()))]
pub fn force_remove_vote(
origin: OriginFor<T>,
target: crate::AccountIdLookupOf<T>,
index: crate::types::ReferendumIndex,
) -> DispatchResult {
let who = T::VoteRemovalOrigin::ensure_origin(origin)?;
let target = T::Lookup::lookup(target)?;
let scope = if target == who {
crate::types::UnvoteScope::Any
} else {
crate::types::UnvoteScope::OnlyExpired
};
Self::try_remove_vote(&target, index, scope, true)?;
Ok(())
}
}
}

Expand Down Expand Up @@ -1332,7 +1367,12 @@ impl<T: Config> Pallet<T> {
/// - The referendum has finished and the voter's lock period is up.
///
/// This will generally be combined with a call to `unlock`.
fn try_remove_vote(who: &T::AccountId, ref_index: ReferendumIndex, scope: UnvoteScope) -> DispatchResult {
fn try_remove_vote(
who: &T::AccountId,
ref_index: ReferendumIndex,
scope: UnvoteScope,
forced: bool,
) -> DispatchResult {
let info = ReferendumInfoOf::<T>::get(ref_index);
VotingOf::<T>::try_mutate(who, |voting| -> DispatchResult {
if let Voting::Direct {
Expand Down Expand Up @@ -1361,7 +1401,7 @@ impl<T: Config> Pallet<T> {
end.saturating_add(T::VoteLockingPeriod::get().saturating_mul(lock_periods.into()));
let now = frame_system::Pallet::<T>::block_number();
if now < unlock_at {
ensure!(matches!(scope, UnvoteScope::Any), Error::<T>::NoPermission);
ensure!(forced || matches!(scope, UnvoteScope::Any), Error::<T>::NoPermission);
prior.accumulate(unlock_at, balance)
}
} else {
Expand All @@ -1375,7 +1415,7 @@ impl<T: Config> Pallet<T> {
);
let now = frame_system::Pallet::<T>::block_number();
if now < unlock_at {
ensure!(matches!(scope, UnvoteScope::Any), Error::<T>::NoPermission);
ensure!(forced || matches!(scope, UnvoteScope::Any), Error::<T>::NoPermission);
prior.accumulate(unlock_at, to_lock)
}
}
Expand Down
2 changes: 2 additions & 0 deletions pallets/democracy/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ ord_parameter_types! {
pub const Four: u64 = 4;
pub const Five: u64 = 5;
pub const Six: u64 = 6;
pub const Seven: u64 = 7;
}
pub struct OneToFive;
impl SortedMembers<u64> for OneToFive {
Expand Down Expand Up @@ -211,6 +212,7 @@ impl Config for Test {
type MaxProposals = ConstU32<100>;
type Preimages = Preimage;
type DemocracyHooks = HooksHandler;
type VoteRemovalOrigin = EnsureSignedBy<Seven, u64>;
}

pub fn new_test_ext() -> sp_io::TestExternalities {
Expand Down
58 changes: 58 additions & 0 deletions pallets/democracy/src/tests/voting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -194,3 +194,61 @@ fn passing_low_turnout_voting_should_work() {
assert_eq!(Balances::free_balance(42), 2);
});
}

#[test]
fn force_remove_vote_should_work() {
new_test_ext().execute_with(|| {
System::set_block_number(0);
assert_ok!(propose_set_balance(1, 2, 1));
let r = 0;
assert!(Democracy::referendum_info(r).is_none());

// start of 2 => next referendum scheduled.
fast_forward_to(2);
assert_ok!(Democracy::vote(RuntimeOrigin::signed(1), r, big_aye(1)));

assert_eq!(Democracy::referendum_count(), 1);
assert_eq!(
Democracy::referendum_status(0),
Ok(ReferendumStatus {
end: 4,
proposal: set_balance_proposal(2),
threshold: VoteThreshold::SuperMajorityApprove,
delay: 2,
tally: Tally {
ayes: 10,
nays: 0,
turnout: 10
},
})
);

fast_forward_to(3);

// referendum still running
assert_ok!(Democracy::referendum_status(0));

// not allowed to force remove a vote when referendum is ongoing
assert_noop!(
Democracy::force_remove_vote(RuntimeOrigin::signed(7), 1, 0),
Error::<Test>::NoPermission
);

// referendum runs during 2 and 3, ends @ start of 4.
fast_forward_to(4);

assert_noop!(Democracy::referendum_status(0), Error::<Test>::ReferendumInvalid);
assert!(pallet_scheduler::Agenda::<Test>::get(6)[0].is_some());

// referendum passes and wait another two blocks for enactment.
fast_forward_to(6);

assert_eq!(Balances::free_balance(42), 2);

// Not allowed origin
assert_noop!(Democracy::force_remove_vote(RuntimeOrigin::signed(4), 1, 0), BadOrigin);

// Allowed origin
assert_ok!(Democracy::force_remove_vote(RuntimeOrigin::signed(7), 1, 0));
});
}
2 changes: 1 addition & 1 deletion runtime/hydradx/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "hydradx-runtime"
version = "245.0.0"
version = "246.0.0"
authors = ["GalacticCouncil"]
edition = "2021"
license = "Apache 2.0"
Expand Down
2 changes: 2 additions & 0 deletions runtime/hydradx/src/governance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,8 @@ impl pallet_democracy::Config for Runtime {
type PalletsOrigin = OriginCaller;
type Slash = Treasury;
type DemocracyHooks = pallet_staking::integrations::democracy::StakingDemocracy<Runtime>;
// Any single technical committee member may remove a vote.
type VoteRemovalOrigin = frame_system::EnsureSignedBy<TechCommAccounts, AccountId>;
}

parameter_types! {
Expand Down
2 changes: 1 addition & 1 deletion runtime/hydradx/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
spec_name: create_runtime_str!("hydradx"),
impl_name: create_runtime_str!("hydradx"),
authoring_version: 1,
spec_version: 245,
spec_version: 246,
impl_version: 0,
apis: RUNTIME_API_VERSIONS,
transaction_version: 1,
Expand Down