-
Notifications
You must be signed in to change notification settings - Fork 144
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
Vote Delegation #888
Open
NoahSaso
wants to merge
35
commits into
development
Choose a base branch
from
noah/delegations
base: development
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Vote Delegation #888
+8,336
−1,390
Conversation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
f35c887
to
92fed36
Compare
Merged
d46af09
to
ae17307
Compare
2e4cc61
to
92efcc1
Compare
…es and removed redundant state
c045e52
to
56920aa
Compare
81d1785
to
ed4a6f8
Compare
21edc67
to
5cb1d01
Compare
5cb1d01
to
7b8d966
Compare
…ax delegations config
da30bd4
to
c73d7f3
Compare
c73d7f3
to
f80833e
Compare
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
DAO Vote Delegation
The
dao-vote-delegation
contract allows members of a DAO to delegate theirvoting power to other members of the DAO who have registered as delegates. It
works in conjunction with voting and proposal modules to offer a comprehensive
delegation system for DAOs that supports the following features:
delegator.
Instantiation and Setup
This contract must be instantiated by the DAO. It is queried by proposal modules when users cast votes to determine how much voting power to tally on a proposal, in addition to a user's normal voting power derived from the voting module.
Hooks
After instantiating the contract, it is VITAL to set up the required hooks for
it to work. To compute delegate voting power correctly, this contract needs to
know about both voting power changes and votes cast on proposals as soon as they
happen.
This can be achieved using the
add_hook
method on voting/staking contractsthat support voting power changes, such as:
cw4-group
dao-voting-cw721-staked
dao-voting-token-staked
cw20-stake
For proposal modules, the corresponding hook is
add_vote_hook
:dao-proposal-single
dao-proposal-multiple
dao-proposal-condorcet
Design Decisions
Fractional Delegation via Percentages
In order to support fractional delegation, users assign a percentage of voting
power to each delegate. Percentages are used instead of choosing an absolute
amount of voting power (e.g. staked tokens) since voting power can change
independently of delegation. If an absolute amount were used, and a user who had
delegated all of their voting power to a few different delegates then unstaked
half of their tokens, there is no clear way to resolve what their new
delegations are. Using percentages instead allows voting power and delegation to
be decided independently.
Implementation Notes
The trickiest piece of this implementation is navigating the snapshot maps,
which are the data structures used to store historical state.
Essentially, snapshot maps (and the other historical data structures based on
snapshot maps) take 1 block to reflect updates made, but only when querying
state at a specific height (typically in the past). When using the query
functions that do not accept a height, they read the updates immediately,
including those from the same block. For example,
snapshot_map.may_load
returns the latest map values, including those changed in the same block by an
earlier transaction; on the other hand,
snapshot_map.may_load_at_height
returns the map values as they were at the end of the previous block (due to an
implementation detail of snapshot maps that I'm not sure was intentional).
Ideally, we would just fix this discrepancy and move on. However, many other
modules have been built using SnapshotMaps, and it is important that all modules
behave consistently with respect to this issue. For example, voting power
queries in voting modules operate in this way, with updates delayed 1
block—because of this, it is crucial that we compute and store delegated voting
power in the same way. Otherwise we risk introducing off-by-one inconsistencies
in voting power calculations. Thus, for now, we will accept this behavior and
continue.
What this means for the implementation is that we must be very careful whenever
we do pretty much anything. When performing updates at the latest block, such as
when delegating or undelegating voting power, or when handling a change in
someone's voting power (in order to propagate that change to their delegates),
we need to be sure to interact with the latest delegation and voting power
state. However, when querying information from the past, we need to match the
delayed update behavior of voting power queries.
More concretely:
handling voting power change hooks, we need to access the account's latest
voting power (by querying
latest_height + 1
), even if it was updated in thesame block. this ensures that changes to voting power right before a
registration/delegation occurs, or voting power changes right after a
delegation occurs, are taken into account. e.g. an account should not be able
to get rid of all their voting power (i.e. stop being a member) and then
become a delegate within the same block.
to update a delegate's total delegated VP, we need to query the latest
delegated VP, even if it was updated earlier in the same block, and then
effectively "re-prepare" the total that will be reflected in historical
queries starting from the next block.
snapshot_map.update
takes care of thisautomatically by loading the latest value from the same block.
total unvoted delegated VP when they cast a vote, or when a vote cast hook is
triggered for a delegator, we need to use historical queries that match the
behavior of the voting module's voting power queries, i.e. delayed by 1 block.