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

pallet-conviction-voting: add voting hooks #5735

Open
wants to merge 6 commits into
base: master
Choose a base branch
from

Conversation

enthusiastmartin
Copy link

Description

We, at Hydration, need to perform additional actions when a new vote is registered and existing vote is removed. Our use case is tied to staking, where we need to reward an account for voting.

Therefore, we need to know when a new vote is registered. We propose this PR to add voting hooks to the conviction-voting pallet.

This change does not change the original behavior ( except one special case described below if desired).

In the pallet config, there is a new config parameter:

    type VotingHooks: VotingHooks<Self::AccountId, PollIndexOf<Self, I>, BalanceOf<Self, I>>;

Anything that integrates conviction-voting pallet, can decide to implement the voting hooks and perform desirable action on events such as on_vote and on_remove_vote.

The VotingHooks traits looks like:

pub trait VotingHooks<AccountId, Index, Balance> {
	// Called when vote is executed.
	fn on_vote(who: &AccountId, ref_index: Index, vote: AccountVote<Balance>) -> DispatchResult;

	// Called when removed vote is executed.
	// is_finished indicates the state of the referendum = None if referendum is cancelled, Some(true) if referendum is ongoing and Some(false) when finished.
	fn on_remove_vote(who: &AccountId, ref_index: Index, ongoing: Option<bool>);

	// Called when removed vote is executed and voter lost the direction to possibly lock some balance.
	// Can return an amount that should be locked for the conviction time.
	fn balance_locked_on_unsuccessful_vote(who: &AccountId, ref_index: Index) -> Option<Balance>;

	#[cfg(feature = "runtime-benchmarks")]
	fn on_vote_worst_case(who: &AccountId);

	#[cfg(feature = "runtime-benchmarks")]
	fn on_remove_vote_worst_case(who: &AccountId);
}

The on_voteand on_remove_vote are pretty self-explanatory and are called when a vote is added/removed.

There is a hook called balance_locked_on_unsuccessful_vote. This is a special hook that is called when a vote that is being removed was in opposition to the winning vote.
The result of this function can return a balance and that amount will be locked for the conviction time. This is the only change that modifies original behavior of the pallet where there was no locking in such case.

It is a special case, where a chain can decide that it needs to lock some balance even in the case of not-winning vote. We, at Hydration, use these hooks with staking functionality where we require to lock too in such cases. Obviously, returning None by defaut, completely disables this feature and its behavior remains as it is.

Integration

To integrate VotingHooks and to perform certain actions as described above, it is required to implement corresponding trait.

Otherwise, simple

    type VotingHooks = ();

can be used and conviction-voting pallet works as nothing happens.

Review Notes

The hooks are called in try_vote and try_remove_vote with parameters.

on_vote hook needs an account that voted on referendum identified by ref_index and the vote itself.

    fn on_vote(who: &AccountId, ref_index: Index, vote: AccountVote<Balance>) -> DispatchResult;

on_remove_vote needs an account that is removing the vote from referendum identified by ref_index. Also bool flag is added to provide information if the referendum is still ongoing.

    fn on_remove_vote(who: &AccountId, ref_index: Index, ongoing: Option<bool>);

ongoing flag is an option because value None is used when referendum has been cancelled.

Tests

Unit tests are added to verify that each hooks is called with correct parameters.

Benchmarks

VotingHook trait contains:

	#[cfg(feature = "runtime-benchmarks")]
	fn on_vote_worst_case(who: &AccountId);

	#[cfg(feature = "runtime-benchmarks")]
	fn on_remove_vote_worst_case(who: &AccountId);

These are called in benchmarks to set up worst cases for the hooks.

Question/Consideration: WE considered adding BenchmarkHelper instead of having the runtime-benchmarks functions in the trait. But I believe this is easier to understand when and why it needs to implement worst cases scenarios ( only if voting hook is used).

@enthusiastmartin enthusiastmartin requested a review from a team as a code owner September 16, 2024 19:30
@@ -0,0 +1,40 @@
use crate::AccountVote;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing license header.

Comment on lines +4 to +21
pub trait VotingHooks<AccountId, Index, Balance> {
// Called when vote is executed.
fn on_vote(who: &AccountId, ref_index: Index, vote: AccountVote<Balance>) -> DispatchResult;

// Called when removed vote is executed.
// is_finished indicates the state of the referendum = None if referendum is cancelled, Some(true) if referendum is ongoing and Some(false) when finished.
fn on_remove_vote(who: &AccountId, ref_index: Index, ongoing: Option<bool>);

// Called when removed vote is executed and voter lost the direction to possibly lock some balance.
// Can return an amount that should be locked for the conviction time.
fn balance_locked_on_unsuccessful_vote(who: &AccountId, ref_index: Index) -> Option<Balance>;

#[cfg(feature = "runtime-benchmarks")]
fn on_vote_worst_case(who: &AccountId);

#[cfg(feature = "runtime-benchmarks")]
fn on_remove_vote_worst_case(who: &AccountId);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please improve the docs.


// Called when removed vote is executed.
// is_finished indicates the state of the referendum = None if referendum is cancelled, Some(true) if referendum is ongoing and Some(false) when finished.
fn on_remove_vote(who: &AccountId, ref_index: Index, ongoing: Option<bool>);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ongoing should be an enum that expresses the state better.

Copy link
Contributor

@ndkazu ndkazu Feb 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bkchr, My plan is to replace the bool by a generic, and use the trait Config to bring in the enum ReferendumInfo from pallet_referenda:
In conviction_voting runtime implementation, I see that the type Polls is defined as Referenda. is it possible through Polls in the trait Config to bring the enum ReferendumInfo in conviction_voting and use it here? or do we necessarily need to add the corresponding type in the runtime implementation & the trait config?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't really understand your plan. Why not just make it an enum as I said above?

Copy link
Contributor

@ndkazu ndkazu Feb 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the enum items are the same I just thought it would be nice to re-use what we already have in pallet-referenda. And as we have a connection between conviction-voting and referenda in the runtime implementation....

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pallets are not tightly coupled and I also don't see a reason why we should do this here now. Let's try some custom enum for now :)

@@ -487,10 +496,36 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
);
prior.accumulate(unlock_at, balance)
}
} else if v.1.as_standard() == Some(!approved) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

locked_if should be changed to take an enum

enum LockedIf {
     Status(bool),
     Always,
}

Always would only be set when the balance_locked_on_unsuccessful_vote returns so.

Comment on lines +445 to +446
// Call on_vote hook
T::VotingHooks::on_vote(who, poll_index, vote)?;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would move this outside of try_access_poll call

@bkchr
Copy link
Member

bkchr commented Feb 20, 2025

@jak-pan will someone from your side finish this or should I do this?

@ndkazu ndkazu mentioned this pull request Feb 25, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants