SNS Governance feature proposal: show the neuron reward portion of each reward event so that SNSs can pay stakeholders in proportion to the voting participation

Hello and greetings!

This post is a proposal for a feature for the SNS governance canister.

If a SNS wants to pay it’s stakeholders with profits generated in the SNS, currently it is possible to do that by making the maturity rewards low enough that the user cannot disburse the maturity, while keeping the maturity a small enough amount so that it’s possible to view the voting participation. This option has a couple of drawbacks. First, it is not possible to give neuron holders regular (inflation based) voting rewards while using this method, and second, the maturity might eventually grow big enough for a user to disburse it even with the small amounts and then it will be impossible to calculate voting participation.

The CYCLES-TRANSFER-STATION gives standard (inflation based) voting rewards in addition to payouts of the profits generated within the CTS market, so a new method of reading the voting participation of SNS neurons is needed.

This proposal makes it possible to give standard (inflation based) voting rewards where users can disburse maturity at any time, and at the same time the SNS can view the voting participation of each neuron and share profits in proportion to the voting participation.

The implementation of this feature stores the neuron’s reward (neuron_reward_e8s) of the latest 5 reward events on each neuron. Each neuron will have a new field reward_events_to_neuron_reward_e8s which is a protobuf: map<uint64, uint64>, rust: BTreeMap<u64, u64> where the map keys are the reward_event_end_timestamp_seconds of that reward-event, and the map values are the neuron_reward_e8s for that neuron for that reward-event.

Here is the draft PR implementation of this feature:

Here is the plan:

  • Gather feedback and crystalize the implementation specifics.
  • Write tests.
  • Reviewers final review.
  • Publish the feature in the SNS governance canister.

Tagging DFINITY team members @lara, @msumme, @DanielThurau, @bjoernek, @bjoern.

Looking forward for your feedback, grinding this out, and working as a community!

:Levi.

I wonder if it is already possible to accomplish this. I think your goal is to both see the voting participation of each neuron, and pay each neuron or its controller occasionally with SNS tokens. I am thinking that this can be accomplished with the following capabilities of the SNS:

  1. Enumerating all neurons with list_neurons.
  2. Seeing which neurons have voted on a particular proposal using get_proposal. (Proposals are garbage-collected a bit after they are decided, so you will need to record this information elsewhere.)
  3. Minting tokens with MintSnsTokens proposals. (Perhaps minted to a canister which then handles the logic of distributing the tokens.)

However I’m not sure if I’ve understood your desires so let me know what you think.

The profits generated within the SNS-dapp are in the form of many different tokens (not this SNS’s tokens) and these profits are payed out to the SNS neuron-holders in the same proportions as the voting rewards. So if a neuron got 1/100 of the total standard inflation-based voting rewards for a specific sns-reward-event, then the SNS-dapp will pay that neuron’s-controller 1/100 of the profits collected within that reward-event in the form of the many different tokens.

I see, do you think the general outline I described will still work?

If 30 proposals are created and executed within a span of 2 minutes, is there a guaranteed time frame that a caller canister can get the ballots of each proposal? If there is how long is this time frame? Would it require a caller canister polling the SNS governance every 10 seconds? If so it would be a big unnecessary waste of compute resources. The feature in the original-post makes it so that a caller canister can sync with the SNS governance canister once a day or so.

Hi @levi,
I don’t want to distract too much from the discussion with Andre, but to set expectations I wanted to clarify that unfortunately it is not yet possible for people outside DFINITY to make PRs to this repository.

I propose we continue the discussion to better understand what you are looking for and to what extent this can already be achieved with the current version of the governance canister. If then more is needed, we can discuss whether we can add this to the SNS roadmap.

Ballots are only cleared when rewards are distributed, so you just need to check after the proposal is no longer accepting votes but before reward distribution happens.

Hi @Andre-Popovitch, There is no way to time when a proposal will be decided, it can happen that a bunch of proposals change into the “no longer accepting votes” status right before a sns-reward-event is triggered where the ballots get cleared, and then there is no time for a caller to sync with the votes. Even just trying to do that would require constant polling every few seconds to the sns-governance canister and would still not guarantee that the caller can sync with every ballot before they get cleared.

It is worth noting that proposals accept votes even after they are decided. They continue to accept votes until the proposal deadline is reached and are not eligible for garbage collection until after the proposal deadline has been reached.

I don’t think it would require constant polling, it only requires polling just before the reward event happens. It is conceivably possible that a proposal deadline is at the exact same consensus round as the reward distribution, and someone also votes during this consensus round, which would make it impossible to record that user’s vote. But this seems like a somewhat unlikely scenario IMO. To remedy this, it may be possible to cause proposals to not be garbage collected until the reward round after the one they completed in, or something like that. What do you think?

A reward event can happen at any time. The code in the SNS governance canister here: ic/rs/sns/governance/src/governance.rs at c424516728b3734d135a2bd22eb223e7e54b1be5 · dfinity/ic · GitHub and here: ic/rs/sns/governance/src/governance.rs at c424516728b3734d135a2bd22eb223e7e54b1be5 · dfinity/ic · GitHub says that a reward event will happen if seconds_since_last_reward_event > round_duration_seconds (where round_duration_seconds is set to 1-day on every SNS) and if there is at least one proposal ready to settle. This means that as long as it’s been at least 1 day since the last reward event, as soon as there is a proposal ready to settle (no longer accepting votes, ready to distributed rewards for it) which can happen at any time, then it will do a reward event.

Trying to time when after the previous reward-event will the first proposal become ready to settle would require trying to keep up with the current voting patterns for every proposal through constant polling, and would be a very fragile implementation in my view. Proposals have a wait-for-quiet mechanism that can extend the proposals lifetime, during which a proposal submitted after the first one, can become ready-to-settle before the first one.

I think the confusion comes from an assumption that a proposal no longer accepts votes once it is decided. It’s true that a proposal can be decided at any time, but proposals continue to accept votes until the proposal deadline.

And the proposal deadline is at minimum the initial_voting_period_seconds and at most the initial_voting_period_seconds + 2 * wait_for_quiet_deadline_increase_seconds:

For your purposes, I would recommend setting wait_for_quiet_deadline_increase_seconds to 0, so that all proposals’ deadline is exactly initial_voting_period_seconds after they are created. This means that you just need to poll list_proposals once per initial_voting_period_seconds / 2, then schedule two get_proposal calls at just before and just after the proposal’s deadline. This will allow you to capture all of the votes in almost all cases, with the remaining cases being the final votes for the few proposals whose deadline coincides almost exactly with a reward event round.

BTW this is not quite true, reward events happen even if there is no proposal ready to settle. This means such a reward event is basically a no-op, except that it cause the next reward event to not happen for another round_duration_seconds. See this comment:

This makes it unlikely that a proposal’s deadline will coincide at exactly the same time as a reward event.

Good catch thanks, so this means we know when a reward event will happen around the same time every day but we still don’t have a way to guarantee that we can count every vote.

Also we like the wait-for-quiet mechanism and want to keep it, so that makes it harder to keep track of when proposals stop accepting votes.

One thing that would guarantee that we can count all the votes is what you mentioned here:

I think that is a great idea. This is in the same direction of the solution in the original-post, which is to group the voting data by reward-event. The solution in the original post does this by saving only one value per neuron per reward-event instead of saving many proposals per neuron per reward-event.

You can still accomplish something like what I laid out in the presence of wait-for-quiet, it just becomes slightly more complicated. You can grab the [wait_for_quiet_state[(ic/rs/sns/governance/proto/ic_sns_governance/pb/v1/governance.proto at ceefdcefd2219999da2ff6418d963874e3675f86 · dfinity/ic · GitHub) by calling GetProposal and see when the deadline is scheduled. Then you can check again around the observed deadline and see if deadline has increased. If it has, then you schedule a call to check again, etc. Since the deadline is nondecreasing, this will prevent you from missing the proposal deadline and not getting all the votes.

Sounds good, I will bring this to the team. In the meantime, if you try out my suggestion please let me know how it goes