-
Notifications
You must be signed in to change notification settings - Fork 81
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
Add DAO data model architecture #165
Open
ctindogaru
wants to merge
1
commit into
near-daos:main
Choose a base branch
from
ctindogaru:dao_data_model
base: main
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
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,227 @@ | ||
> NOTE: The following is implementation examples for discussion on future models for DAOs | ||
|
||
## DAO Registry Example | ||
```rust | ||
// This is just extending the normal dao data, allowing registries to offload logic & state | ||
pub struct DAOContract { | ||
/// Governance modules are unowned math libraries that are only responsible for data computation. They follow a rigid definition governance spec/interface that the DAO can utilize within proposals. | ||
pub governance_modules: UnorderedSet<AccountId>, | ||
/// Extensions keep track of registry of DAO approved extensions (keys), where state requirements are wrapped in value - if specific data needs to remain within the DAO vs inside the extension. (Think of DAO preferences or pagination indexes) | ||
pub extension_modules: UnorderedMap<AccountId, Vec<u8>>, | ||
|
||
/// NOTE: Policy may need changes to support diff groups control over governance modules/extensions | ||
} | ||
``` | ||
|
||
|
||
## DAO Proposal Data | ||
NOTE: this would utilize the "VersionedProposal" setup, adding a new version | ||
|
||
NOTE: Need to consider items brought up in: https://github.com/near-daos/sputnik-dao-contract/issues/161 | ||
|
||
```rust | ||
pub struct Proposal { | ||
// ---- ORIGINAL UNCHANGING ---------------------------- | ||
/// Original proposer. | ||
pub proposer: AccountId, | ||
/// Description of this proposal. | ||
pub description: String, | ||
/// Kind of proposal with relevant information. | ||
pub kind: ProposalKind, | ||
/// Current status of the proposal. | ||
pub status: ProposalStatus, | ||
/// Submission time (for voting period). | ||
pub submission_time: U64, | ||
// ---- CHANGING PROPOSAL ---------------------------- | ||
/// DEPRECATE: Count of votes per role per decision: yes / no / spam. (Can be safely deprecated upon all active proposals being finalized) | ||
pub vote_counts: HashMap<String, [Balance; 3]>, | ||
/// DEPRECATE: Map of who voted and how. (Can be safely deprecated upon all active proposals being finalized) | ||
pub votes: HashMap<AccountId, Vote>, | ||
// ---- NEW PROPOSAL DATA ---------------------------- | ||
/// The module used to compute data against | ||
pub governance_module: AccountId, | ||
/// Proofs are votes with a more discrete / flexible payload the math modules can utilize | ||
/// NOTE: Map allows for ballot edits up until proposal finalization time | ||
pub ballots: UnorderedMap<AccountId, Ballot>, | ||
/// Computed index-based tally totals | ||
pub outcome: Outcome, | ||
/// metadata, allowing extensions or other innovations | ||
/// NOTE: This should allow URIs to IPFS docs or other proposal data needed for governance decisions | ||
pub metadata: Option<Vec<u8>>, | ||
} | ||
``` | ||
|
||
|
||
## New Proposal Kinds | ||
```rust | ||
pub enum ProposalKind { | ||
/// Enable conviction weight decisions to trigger differing functions/actions, | ||
/// This will still only execute 1 proposal kind, however it allows the members | ||
/// to decide the path based on multiple choice (or similar) | ||
IndexTriggeredActions(Vec<ProposalKind>), | ||
} | ||
``` | ||
|
||
|
||
## Governance Module Interface | ||
```rust | ||
// no state please, math only | ||
// no owners allowed, confirm no access keys before use | ||
pub trait Governance { | ||
/// Implements a function that call tally a set of data, returning the collated value | ||
pub fn compute(&self, ballots: Base64VecU8) -> PromiseOrValue<Outcome>; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agree with this interface, but |
||
} | ||
``` | ||
|
||
|
||
## Extension Module Interface | ||
```rust | ||
// allows state, can allow iterative compute triggered/paginated outside the vote cycles | ||
// owners allowed but highly discouraged, confirm no access keys before use | ||
// Extensions are awesome when additional state is needed for proposals. | ||
// Great usage will allow things like a cyclical proposal which includes a cooldown period | ||
// that allows a 7-day extra window (for example) which could incorporate arbitration to correct | ||
// a poor outcome, given some failure of governance or allowing 3rd party audit/weight post-vote. | ||
// It allows for governance to have a safety or insurance window for remediation | ||
// State-channel-like implementations should be the core of this type of module, however it could | ||
// get as complicated as handling active strategies on a DeFi protocol | ||
pub trait Extension { | ||
/// All contextual data is submitted at proposal creation | ||
/// Pre-vote state | ||
pub fn prepare(&mut self, proposal_id: u128, config: Base64VecU8); | ||
/// Implements a function that call tally a set of data, returning the collated value, including multi-computed values from state | ||
/// Active-vote state | ||
pub fn compute(&self, ballots: Base64VecU8) -> PromiseOrValue<Outcome>; | ||
/// Post-processing & | ||
/// post-vote & pre-finalization state | ||
pub fn fulfill(&mut self, proposal_id: u128, proposal: Proposal); | ||
/// External actions based on proposal outcome data, after fulfill handles any additional vote-period closure | ||
/// finalization state | ||
pub fn finalize(&mut self, proposal_id: u128, proposal: Proposal); | ||
} | ||
``` | ||
|
||
|
||
## Outcome Spec | ||
```rust | ||
pub enum OutcomeKind { | ||
// For tracking [VoteApprove, VoteReject, VoteRemove] | ||
StdVote, | ||
// For tracking signaling based on dynamic length indexes | ||
IndexWeighted, | ||
} | ||
|
||
pub struct Outcome { | ||
kind: OutcomeKind, | ||
|
||
// NOTE: Needs to support balance level numbers in case votes are token weighted | ||
// NOTE: Index is critical for computing tallies | ||
/// flexible tallies computed by external module | ||
totals: Vec<u128>, | ||
|
||
/// Starts empty, until minimum threshold ballots is available | ||
finality_index: Option<usize>, | ||
} | ||
|
||
/// Examples: | ||
// v2 voting style, where totals is tallies for: [VoteApprove, VoteReject, VoteRemove] | ||
let vote_outcome = Outcome { | ||
kind: OutcomeKind::StdVote, | ||
totals: vec![12, 4, 1], | ||
finality_index: None, | ||
}; | ||
// votes are counted at indexes, can utilize many governance modules to arrive at these counts | ||
let indexed_outcome = Outcome { | ||
kind: OutcomeKind::IndexWeighted, | ||
totals: vec![1, 3, 19, 2], | ||
finality_index: Some(2), | ||
}; | ||
``` | ||
|
||
|
||
## Ballot Data Example | ||
```rust | ||
pub struct Ballot { | ||
// NOTE: Needs to support balance level numbers in case votes are token weighted | ||
// NOTE: Index is critical for computing tallies | ||
weights: Vec<u128>, | ||
|
||
// Generic data | ||
// Allows for special ballot features like commit+reveal payloads | ||
// Data will ONLY be interpretable by the governance module | ||
data: Option<Base64VecU8> | ||
} | ||
``` | ||
|
||
# Module Examples | ||
|
||
## Governance Module Demo: Simple Math Voting | ||
```rust | ||
struct Governance {} | ||
|
||
impl Governance { | ||
/// Implements a function that call tally a set of data, returning the collated value | ||
// NOTE: this is pseudo-code - completely untested note-style code for reference of ideas | ||
pub fn compute(&self, ballots: &mut Base64VecU8) -> PromiseOrValue<Outcome> { | ||
// Extract the ballots | ||
let all_ballots: Ballot = serde_json::de::from_slice(&ballots).expect("Bad thing"); | ||
let mut outcome = Outcome::default(); | ||
|
||
// Loop and tally | ||
all_ballots.iter().map(|(p, i)| { | ||
// NOTE: This is where things can get completely custom. | ||
// Could just do a simple tally, addition, multiplication - it is up to implementation requirements | ||
outcome.totals[i] += p[i]; | ||
}); | ||
|
||
// Compute latest winning index | ||
let max: u128 = outcome.totals.iter().max(); | ||
outcome.winning_index = Some(outcome.totals.iter().position(|&x| x == max)); | ||
|
||
PromiseOrValue::Value(Base64VecU8::from(outcome.to_string())) | ||
}; | ||
} | ||
``` | ||
|
||
## Extension Module Demo: Simple Math Voting | ||
```rust | ||
struct Extension {} | ||
|
||
impl Extension { | ||
/// All contextual data is submitted at proposal creation | ||
/// Pre-vote state | ||
pub fn prepare(&mut self, proposal_id: u128, config: Base64VecU8) { | ||
// TODO: | ||
}; | ||
/// Implements a function that call tally a set of data, returning the collated value | ||
// NOTE: this is pseudo-code - completely untested note-style code for reference of ideas | ||
pub fn compute(&self, ballots: &mut Base64VecU8) -> PromiseOrValue<Outcome> { | ||
// Extract the ballots | ||
let all_ballots: Ballot = serde_json::de::from_slice(&ballots).expect("Bad thing"); | ||
let mut outcome = Outcome::default(); | ||
|
||
// Loop and tally | ||
all_ballots.iter().map(|(p, i)| { | ||
// NOTE: This is where things can get completely custom. | ||
// Could just do a simple tally, addition, multiplication - it is up to implementation requirements | ||
outcome.totals[i] += p[i]; | ||
}); | ||
|
||
// Compute latest winning index | ||
let max: u128 = outcome.totals.iter().max(); | ||
outcome.winning_index = Some(outcome.totals.iter().position(|&x| x == max)); | ||
|
||
PromiseOrValue::Value(Base64VecU8::from(outcome.to_string())) | ||
}; | ||
/// Post-processing & | ||
/// post-vote & pre-finalization state | ||
pub fn fulfill(&mut self, proposal_id: u128, proposal: Proposal) { | ||
// TODO: | ||
}; | ||
/// External actions based on proposal outcome data, after fulfill handles any additional vote-period closure | ||
/// finalization state | ||
pub fn finalize(&mut self, proposal_id: u128, proposal: Proposal) { | ||
// TODO: | ||
}; | ||
} | ||
``` |
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we only need
governance_module
and the existingvotes
here. I don't seeballots
,outcome
,metadata
as necessary.Then, in the
governance_module
you would have a method calledcompute(votes)
and also a state (to represent the weights for each member, the governance token etc.).With that in mind, you would call
compute(votes)
every time whenact_proposal
is called. Basically, this portion of the code will be replaced with a call tocompute(votes)
: