Governance program API design
Co-authored-by: Michael Vines <mvines@gmail.com>
This commit is contained in:
parent
1beeb9fd21
commit
e5af52d6e7
|
@ -9,3 +9,4 @@ hfuzz_target
|
|||
hfuzz_workspace
|
||||
**/*.so
|
||||
**/.DS_Store
|
||||
test-ledger
|
||||
|
|
|
@ -3804,6 +3804,19 @@ dependencies = [
|
|||
"spl-feature-proposal",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spl-governance"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"assert_matches",
|
||||
"num-derive",
|
||||
"num-traits",
|
||||
"solana-program",
|
||||
"solana-program-test",
|
||||
"solana-sdk",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spl-math"
|
||||
version = "0.1.0"
|
||||
|
|
|
@ -9,6 +9,7 @@ members = [
|
|||
"examples/rust/transfer-lamports",
|
||||
"feature-proposal/program",
|
||||
"feature-proposal/cli",
|
||||
"governance/program",
|
||||
"libraries/math",
|
||||
"memo/program",
|
||||
"name-service/program",
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
# Governance
|
||||
|
||||
Governance is a program the chief purpose of which is to control the upgrade of other programs through democratic means.
|
||||
It can also be used as an authority provider for mints and other forms of access control as well where we may want
|
||||
a voting population to vote on disbursement of access or funds collectively.
|
||||
|
||||
## Architecture
|
||||
|
||||
### Accounts diagram
|
||||
|
||||

|
||||
|
||||
### Governance Realm account
|
||||
|
||||
Governance Realm ties Community Token Mint and optional Council Token mint to create a realm
|
||||
for any governance pertaining to the community of the token holders.
|
||||
For example a trading protocol can issue a governance token and use it to create its governance realm.
|
||||
|
||||
Once a realm is created voters can deposit Governing tokens (Community or Council) to the realm and
|
||||
use the deposited amount as their voting weight to vote on Proposals within that realm.
|
||||
|
||||
### Program Governance account
|
||||
|
||||
The basic building block of governance to update programs is the ProgramGovernance account.
|
||||
It ties a governed Program ID and holds configuration options defining governance rules.
|
||||
The governed Program ID is used as the seed for a [Program Derived Address](https://docs.solana.com/developing/programming-model/calling-between-programs#program-derived-addresses),
|
||||
and this program derived address is what is used as the address of the Governance account for your Program ID
|
||||
and the corresponding Governance mint and Council mint (if provided).
|
||||
|
||||
What this means is that there can only ever be ONE Governance account for a given Program.
|
||||
The governance program validates at creation time of the Governance account that the current upgrade authority of the program
|
||||
taken under governance signed the transaction.
|
||||
|
||||
Note: In future versions, once allowed in solana runtime, the governance program will take over the upgrade authority
|
||||
of the governed program when the Governance account is created.
|
||||
|
||||
### How does authority work?
|
||||
|
||||
Governance can handle arbitrary executions of code, but it's real power lies in the power to upgrade programs.
|
||||
It does this through executing commands to the bpf-upgradable-loader program.
|
||||
Bpf-upgradable-loader allows any signer who has Upgrade authority over a Buffer account and the Program account itself
|
||||
to upgrade it using its Upgrade command.
|
||||
Normally, this is the developer who created and deployed the program, and this creation of the Buffer account containing
|
||||
the new program data and overwriting of the existing Program account's data with it is handled in the background for you
|
||||
by the Solana program deploy cli command.
|
||||
However, in order for Governance to be useful, Governance now needs this authority.
|
||||
|
||||
### Proposal accounts
|
||||
|
||||
A Proposal is an instance of a Governance created to vote on and execute given set of changes.
|
||||
It is created by someone (Proposal Admin) and tied to a given Governance account
|
||||
and has a set of executable commands to it, a name and a description.
|
||||
It goes through various states (draft, voting, executing) and users can vote on it
|
||||
if they have relevant Community or Council tokens.
|
||||
It's rules are determined by the Governance account that it is tied to, and when it executes,
|
||||
it is only eligible to use the [Program Derived Address](https://docs.solana.com/developing/programming-model/calling-between-programs#program-derived-addresses)
|
||||
authority given by the Governance account.
|
||||
So a Proposal for Sushi cannot for instance upgrade the Program for Uniswap.
|
||||
|
||||
When a Proposal is created by a user then the user becomes Proposal Admin and receives an Admin an Signatory token.
|
||||
With this power the Admin can add other Signatories to the Proposal.
|
||||
These Signatories can then add commands to the Proposal and/or sign off on the Proposal.
|
||||
Once all Signatories have signed off on the Proposal the Proposal leaves Draft state and enters Voting state.
|
||||
Voting state lasts as long as the Governance has it configured to last, and during this time
|
||||
people holding Community (or Council) tokens may vote on the Proposal.
|
||||
Once the Proposal is "tipped" it either enters the Defeated or Executing state.
|
||||
If Executed, it enters Completed state once all commands have been run.
|
||||
|
||||
A command can be run by any one at any time after the `instruction_hold_up_time` length has transpired on the given command.
|
||||
|
||||
### SingleSignerInstruction
|
||||
|
||||
We only support one kind of executable command right now, and this is the `SingleSignerInstruction` type.
|
||||
A Proposal can have a certain number of these, and they run independently of each other.
|
||||
These contain the actual data for a command, and how long after the voting phase a user must wait before they can be executed.
|
||||
|
||||
### Voting Dynamics
|
||||
|
||||
When a Proposal is created and signed by its Signatories voters can start voting on it using their voting weight,
|
||||
equal to deposited governing tokens into the realm. A vote is tipped once it passes the defined `vote_threshold` of votes
|
||||
and enters Succeeded or Defeated state. If Succeeded then Proposal instructions can be executed after they hold_up_time passes.
|
||||
|
||||
Users can relinquish their vote any time during Proposal lifetime, but once Proposal it tipped their vote can't be changed.
|
||||
|
||||
### Community and Councils governing tokens
|
||||
|
||||
Each Governance Realm that gets created has the option to also have a Council mint.
|
||||
A council mint is simply a separate mint from the Community mint.
|
||||
What this means is that users can submit Proposals that have a different voting population from a different mint
|
||||
that can affect the same program. A practical application of this policy may be to have a very large population control
|
||||
major version bumps of Solana via normal SOL, for instance, but hot fixes be controlled via Council tokens,
|
||||
of which there may be only 30, and which may be themselves minted and distributed via proposals by the governing population.
|
||||
|
||||
### Proposal Workflow
|
||||
|
||||

|
|
@ -0,0 +1,29 @@
|
|||
[package]
|
||||
name = "spl-governance"
|
||||
version = "0.1.0"
|
||||
description = "Solana Program Library Governance"
|
||||
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
|
||||
repository = "https://github.com/solana-labs/solana-program-library"
|
||||
license = "Apache-2.0"
|
||||
edition = "2018"
|
||||
|
||||
|
||||
[features]
|
||||
no-entrypoint = []
|
||||
test-bpf = []
|
||||
|
||||
|
||||
[dependencies]
|
||||
solana-program = "1.6.7"
|
||||
thiserror = "1.0"
|
||||
num-derive = "0.3"
|
||||
num-traits = "0.2"
|
||||
|
||||
[dev-dependencies]
|
||||
assert_matches = "1.5.0"
|
||||
solana-program-test = "1.6.7"
|
||||
solana-sdk = "1.6.7"
|
||||
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "lib"]
|
|
@ -0,0 +1,2 @@
|
|||
[target.bpfel-unknown-unknown.dependencies.std]
|
||||
features = []
|
|
@ -0,0 +1,22 @@
|
|||
//! Program entrypoint definitions
|
||||
#![cfg(all(target_arch = "bpf", not(feature = "no-entrypoint")))]
|
||||
|
||||
use crate::{error::GovernanceError, processor};
|
||||
use solana_program::{
|
||||
account_info::AccountInfo, entrypoint, entrypoint::ProgramResult,
|
||||
program_error::PrintProgramError, pubkey::Pubkey,
|
||||
};
|
||||
|
||||
entrypoint!(process_instruction);
|
||||
fn process_instruction(
|
||||
program_id: &Pubkey,
|
||||
accounts: &[AccountInfo],
|
||||
instruction_data: &[u8],
|
||||
) -> ProgramResult {
|
||||
if let Err(error) = processor::process_instruction(program_id, accounts, instruction_data) {
|
||||
// catch the error so we can print it
|
||||
error.print::<GovernanceError>();
|
||||
return Err(error);
|
||||
}
|
||||
Ok(())
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
//! Error types
|
||||
|
||||
use num_derive::FromPrimitive;
|
||||
use solana_program::{
|
||||
decode_error::DecodeError,
|
||||
msg,
|
||||
program_error::{PrintProgramError, ProgramError},
|
||||
};
|
||||
use thiserror::Error;
|
||||
|
||||
/// Errors that may be returned by the Governance program.
|
||||
#[derive(Clone, Debug, Eq, Error, FromPrimitive, PartialEq)]
|
||||
pub enum GovernanceError {
|
||||
/// Invalid instruction passed to program.
|
||||
#[error("Invalid instruction passed to program")]
|
||||
InvalidInstruction,
|
||||
}
|
||||
|
||||
impl PrintProgramError for GovernanceError {
|
||||
fn print<E>(&self) {
|
||||
msg!(&self.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
impl From<GovernanceError> for ProgramError {
|
||||
fn from(e: GovernanceError) -> Self {
|
||||
ProgramError::Custom(e as u32)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DecodeError<T> for GovernanceError {
|
||||
fn type_of() -> &'static str {
|
||||
"Governance Error"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,245 @@
|
|||
//! Program instructions
|
||||
|
||||
use solana_program::{instruction::Instruction, pubkey::Pubkey};
|
||||
|
||||
use crate::state::enums::GoverningTokenType;
|
||||
|
||||
/// Yes/No Vote
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum Vote {
|
||||
/// Yes vote
|
||||
Yes,
|
||||
/// No vote
|
||||
No,
|
||||
}
|
||||
|
||||
/// Instructions supported by the Governance program
|
||||
#[derive(Clone)]
|
||||
#[repr(C)]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum GovernanceInstruction {
|
||||
/// Creates Governance Realm account which aggregates governances for given Community Mint and optional Council Mint
|
||||
///
|
||||
/// 0. `[writable]` Governance Realm account. PDA seeds:['governance',name]
|
||||
/// 1. `[]` Community Token Mint
|
||||
/// 2. `[writable]` Community Token Holding account. PDA seeds: ['governance',realm,community_mint]
|
||||
/// The account will be created with the Realm PDA as its owner
|
||||
/// 3. `[signer]` Payer
|
||||
/// 4. `[]` System
|
||||
/// 5. `[]` SPL Token
|
||||
/// 6. `[]` Sysvar Rent
|
||||
/// 7. `[]` Council Token Mint - optional
|
||||
/// 8. `[writable]` Council Token Holding account - optional. . PDA seeds: ['governance',realm,council_mint]
|
||||
/// The account will be created with the Realm PDA as its owner
|
||||
CreateRealm {
|
||||
/// UTF-8 encoded Governance Realm name
|
||||
name: String,
|
||||
},
|
||||
|
||||
/// Deposits governing tokens (Community or Council) to Governance Realm and establishes your voter weight to be used for voting within the Realm
|
||||
/// Note: If subsequent (top up) deposit is made and there are active votes for the Voter then the vote weights won't be updated automatically
|
||||
/// It can be done by relinquishing votes on active Proposals and voting again with the new weight
|
||||
///
|
||||
/// 0. `[]` Governance Realm account
|
||||
/// 1. `[writable]` Governing Token Holding account. PDA seeds: ['governance',realm, governing_token_mint]
|
||||
/// 2. `[writable]` Governing Token Source account. All tokens from the account will be transferred to the Holding account
|
||||
/// 3. `[signer]` Governing Token Owner account
|
||||
/// 4. `[writable]` Voter Record account. PDA seeds: ['governance',realm, governing_token_mint, governing_token_owner]
|
||||
/// 5. `[signer]` Payer
|
||||
/// 6. `[]` System
|
||||
/// 7. `[]` SPL Token
|
||||
DepositGoverningTokens {},
|
||||
|
||||
/// Withdraws governing tokens (Community or Council) from Governance Realm and downgrades your voter weight within the Realm
|
||||
/// Note: It's only possible to withdraw tokens if the Voter doesn't have any outstanding active votes
|
||||
/// If there are any outstanding votes then they must be relinquished before tokens could be withdrawn
|
||||
///
|
||||
/// 0. `[]` Governance Realm account
|
||||
/// 1. `[writable]` Governing Token Holding account. PDA seeds: ['governance',realm, governing_token_mint]
|
||||
/// 2. `[writable]` Governing Token Destination account. All tokens will be transferred to this account
|
||||
/// 3. `[signer]` Governing Token Owner account
|
||||
/// 4. `[writable]` Voter Record account. PDA seeds: ['governance',realm, governing_token_mint, governing_token_owner]
|
||||
/// 5. `[]` SPL Token
|
||||
WithdrawGoverningTokens {},
|
||||
|
||||
/// Sets vote authority for the given Realm and Governing Token Mint (Community or Council)
|
||||
/// The vote authority would have voting rights and could vote on behalf of the Governing Token Owner
|
||||
///
|
||||
/// 0. `[signer]` Governing Token Owner
|
||||
/// 1. `[writable]` Voter Record
|
||||
SetVoteAuthority {
|
||||
#[allow(dead_code)]
|
||||
/// Governance Realm the new vote authority is set for
|
||||
realm: Pubkey,
|
||||
|
||||
#[allow(dead_code)]
|
||||
/// Governing Token Mint the vote authority is granted over
|
||||
governing_token_mint: Pubkey,
|
||||
|
||||
#[allow(dead_code)]
|
||||
/// New vote authority
|
||||
vote_authority: Pubkey,
|
||||
},
|
||||
|
||||
/// Creates Program Governance account which governs an upgradable program
|
||||
///
|
||||
/// 0. `[writable]` Governance account. PDA seeds: ['governance', governed_program]
|
||||
/// 1. `[]` Account of the Program governed by this Governance account
|
||||
/// 2. `[writable]` Program Data account of the Program governed by this Governance account
|
||||
/// 3. `[signer]` Current Upgrade Authority account of the Program governed by this Governance account
|
||||
/// 4. `[]` Governance Realm the Program Governance belongs to
|
||||
/// 5. `[signer]` Payer
|
||||
/// 6. `[]` System account
|
||||
/// 7. `[]` Bpf_upgrade_loader account
|
||||
CreateProgramGovernance {
|
||||
/// Voting threshold in % required to tip the vote
|
||||
/// It's the percentage of tokens out of the entire pool of governance tokens eligible to vote
|
||||
vote_threshold: u8,
|
||||
|
||||
/// Minimum waiting time in slots for an instruction to be executed after proposal is voted on
|
||||
min_instruction_hold_up_time: u64,
|
||||
|
||||
/// Time limit in slots for proposal to be open for voting
|
||||
max_voting_time: u64,
|
||||
|
||||
/// Minimum % of tokens for a governance token owner to be able to create proposal
|
||||
/// It's the percentage of tokens out of the entire pool of governance tokens eligible to vote
|
||||
token_threshold_to_create_proposal: u8,
|
||||
},
|
||||
|
||||
/// Create Proposal account for Instructions that will be executed at various slots in the future
|
||||
/// The instruction also grants Admin and Signatory token to the provided account
|
||||
///
|
||||
/// 0. `[writable]` Uninitialized Proposal account
|
||||
/// 1. `[writable]` Initialized Governance account
|
||||
/// 2. `[writable]` Initialized Signatory Mint account
|
||||
/// 3. `[writable]` Initialized Admin Mint account
|
||||
/// 4. `[writable]` Initialized Admin account for the issued admin token
|
||||
/// 5. `[writable]` Initialized Signatory account for the issued signatory token
|
||||
/// 6. '[]` Token program account
|
||||
/// 7. `[]` Rent sysvar
|
||||
CreateProposal {
|
||||
/// Link to gist explaining proposal
|
||||
description_link: String,
|
||||
|
||||
/// UTF-8 encoded name of the proposal
|
||||
name: String,
|
||||
|
||||
/// The Governing token (Community or Council) which will be used for voting on the Proposal
|
||||
governing_token_type: GoverningTokenType,
|
||||
},
|
||||
|
||||
/// [Requires Admin token]
|
||||
/// Adds a signatory to the Proposal which means this Proposal can't leave Draft state until yet another Signatory signs
|
||||
/// As a result of this call the new Signatory will receive a Signatory Token which then can be used to Sign proposal
|
||||
///
|
||||
/// 0. `[writable]` Proposal account
|
||||
/// 1. `[writable]` Initialized Signatory account
|
||||
/// 2. `[writable]` Initialized Signatory Mint account
|
||||
/// 3. `[signer]` Admin account
|
||||
/// 4. '[]` Token program account
|
||||
AddSignatory,
|
||||
|
||||
/// [Requires Admin token]
|
||||
/// Removes a Signatory from the Proposal
|
||||
///
|
||||
/// 0. `[writable]` Proposal account
|
||||
/// 1. `[writable]` Signatory account to remove token from
|
||||
/// 2. `[writable]` Signatory Mint account
|
||||
/// 3. `[signer]` Admin account
|
||||
/// 4. '[]` Token program account
|
||||
RemoveSignatory,
|
||||
|
||||
/// [Requires Admin token]
|
||||
/// Adds an instruction to the Proposal. Max of 5 of any type. More than 5 will throw error
|
||||
///
|
||||
/// 0. `[writable]` Proposal account
|
||||
/// 1. `[writable]` Uninitialized Proposal SingleSignerInstruction account
|
||||
/// 2. `[signer]` Admin account
|
||||
AddSingleSignerInstruction {
|
||||
/// Slot waiting time between vote period ending and this being eligible for execution
|
||||
hold_up_time: u64,
|
||||
|
||||
/// Instruction
|
||||
instruction: Instruction,
|
||||
|
||||
/// Position in instruction array
|
||||
position: u8,
|
||||
},
|
||||
|
||||
/// [Requires Admin token]
|
||||
/// Remove instruction from the Proposal
|
||||
///
|
||||
/// 0. `[writable]` Proposal account
|
||||
/// 1. `[writable]` Proposal SingleSignerInstruction account
|
||||
/// 2. `[signer]` Admin account
|
||||
RemoveInstruction,
|
||||
|
||||
/// [Requires Admin token]
|
||||
/// Update instruction hold up time in the Proposal
|
||||
///
|
||||
/// 0. `[]` Proposal account
|
||||
/// 1. `[writable]` Proposal SingleSignerInstruction account
|
||||
/// 2. `[signer]` Admin account
|
||||
UpdateInstructionHoldUpTime {
|
||||
/// Minimum waiting time in slots for an instruction to be executed after proposal is voted on
|
||||
hold_up_time: u64,
|
||||
},
|
||||
|
||||
/// [Requires Admin token]
|
||||
/// Cancels Proposal and moves it into Canceled
|
||||
///
|
||||
/// 0. `[writable]` Proposal account
|
||||
/// 1. `[writable]` Admin account
|
||||
CancelProposal,
|
||||
|
||||
/// [Requires Signatory token]
|
||||
/// Burns signatory token, indicating you approve and sign off on moving this Proposal from Draft state to Voting state
|
||||
/// The last Signatory token to be burned moves the state to Voting
|
||||
///
|
||||
/// 0. `[writable]` Proposal account
|
||||
/// 1. `[writable]` Signatory account
|
||||
/// 2. `[writable]` Signatory Mint account
|
||||
/// 3. `[]` Token program account
|
||||
/// 4. `[]` Clock sysvar
|
||||
SignOffProposal,
|
||||
|
||||
/// Uses your voter weight (deposited Community or Council tokens) to cast a vote on a Proposal
|
||||
/// By doing so you indicate you approve or disapprove of running the Proposal set of instructions
|
||||
/// If you tip the consensus then the instructions can begin to be run after their hold up time
|
||||
///
|
||||
/// 0. `[writable]` Proposal account
|
||||
/// 1. `[writable]` Voter Record account. PDA seeds: ['governance',realm, governing_token_mint, governing_token_owner]
|
||||
/// 2. `[writable]` Proposal Vote Record account. PDA seeds: ['governance',proposal,governing_token_owner]
|
||||
/// 3. `[signer]` Vote Authority account
|
||||
/// 4. `[]` Governance account
|
||||
Vote {
|
||||
/// Yes/No vote
|
||||
vote: Vote,
|
||||
},
|
||||
|
||||
/// Relinquish Vote removes voter weight from a Proposal and removes it from voter's active votes
|
||||
/// If the Proposal is still being voted on then the voter's weight won't count towards the vote outcome
|
||||
/// If the Proposal is already in decided state then the instruction has no impact on the Proposal
|
||||
/// and only allows voters to prune their outstanding votes in case they wanted to withdraw Governing tokens from the Realm
|
||||
///
|
||||
/// 0. `[writable]` Proposal account
|
||||
/// 1. `[writable]` Voter Record account. PDA seeds: ['governance',realm, governing_token_mint, governing_token_owner]
|
||||
/// 2. `[writable]` Proposal Vote Record account. PDA seeds: ['governance',proposal,governing_token_owner]
|
||||
/// 3. `[signer]` Vote Authority account
|
||||
RelinquishVote,
|
||||
|
||||
/// Executes an instruction in the Proposal
|
||||
/// Anybody can execute transaction once Proposal has been voted Yes and transaction_hold_up time has passed
|
||||
/// The actual instruction being executed will be signed by Governance PDA
|
||||
/// For example to execute Program upgrade the ProgramGovernance PDA would be used as the singer
|
||||
///
|
||||
/// 0. `[writable]` Proposal account
|
||||
/// 1. `[writable]` Instruction account you wish to execute
|
||||
/// 2. `[]` Program being invoked account
|
||||
/// 3. `[]` Governance account (PDA)
|
||||
/// 4. `[]` Clock sysvar
|
||||
/// 5+ Any extra accounts that are part of the instruction, in order
|
||||
Execute,
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
#![deny(missing_docs)]
|
||||
//! A Governance program for the Solana blockchain.
|
||||
|
||||
pub mod entrypoint;
|
||||
pub mod error;
|
||||
pub mod instruction;
|
||||
pub mod processor;
|
||||
pub mod state;
|
||||
|
||||
// Export current sdk types for downstream users building with a different sdk version
|
||||
pub use solana_program;
|
||||
|
||||
solana_program::declare_id!("Governance111111111111111111111111111111111");
|
|
@ -0,0 +1,14 @@
|
|||
//! Instruction processor
|
||||
|
||||
use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey};
|
||||
|
||||
use crate::error::GovernanceError;
|
||||
|
||||
/// Processes an instruction
|
||||
pub fn process_instruction(
|
||||
_program_id: &Pubkey,
|
||||
_accounts: &[AccountInfo],
|
||||
_input: &[u8],
|
||||
) -> ProgramResult {
|
||||
Err(GovernanceError::InvalidInstruction.into())
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
//! State enumerations
|
||||
|
||||
/// Defines all Governance accounts types
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum GovernanceAccountType {
|
||||
/// Default uninitialized account state
|
||||
Uninitialized,
|
||||
|
||||
/// Top level aggregation for governances with Community Token (and optional Council Token)
|
||||
Realm,
|
||||
|
||||
/// Voter record for each voter and given governing token type within a Realm
|
||||
VoterRecord,
|
||||
|
||||
/// Program Governance account
|
||||
ProgramGovernance,
|
||||
|
||||
/// Proposal account for Governance account. A single Governance account can have multiple Proposal accounts
|
||||
Proposal,
|
||||
|
||||
/// Vote record account for a given Proposal. Proposal can have 0..n voting records
|
||||
ProposalVoteRecord,
|
||||
|
||||
/// Single Signer Instruction account which holds an instruction to execute for Proposal
|
||||
SingleSignerInstruction,
|
||||
}
|
||||
|
||||
impl Default for GovernanceAccountType {
|
||||
fn default() -> Self {
|
||||
GovernanceAccountType::Uninitialized
|
||||
}
|
||||
}
|
||||
|
||||
/// Vote with number of votes
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum VoteWeight {
|
||||
/// Yes vote
|
||||
Yes(u64),
|
||||
|
||||
/// No vote
|
||||
No(u64),
|
||||
}
|
||||
|
||||
/// Governing Token type
|
||||
#[repr(C)]
|
||||
#[derive(Clone)]
|
||||
pub enum GoverningTokenType {
|
||||
/// Community token
|
||||
Community,
|
||||
/// Council token
|
||||
Council,
|
||||
}
|
||||
|
||||
/// What state a Proposal is in
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum ProposalState {
|
||||
/// Draft - Proposal enters Draft state when it's created
|
||||
Draft,
|
||||
|
||||
/// Signing - The Proposal is being signed by Signatories. Proposal enters the state when first Signatory Sings and leaves it when last Signatory signs
|
||||
Signing,
|
||||
|
||||
/// Taking votes
|
||||
Voting,
|
||||
|
||||
/// Voting ended with success
|
||||
Succeeded,
|
||||
|
||||
/// Voting completed and now instructions are being execute. Proposal enter this state when first instruction is executed and leaves when the last instruction is executed
|
||||
Executing,
|
||||
|
||||
/// Completed
|
||||
Completed,
|
||||
|
||||
/// Cancelled
|
||||
Cancelled,
|
||||
|
||||
/// Defeated
|
||||
Defeated,
|
||||
}
|
||||
|
||||
impl Default for ProposalState {
|
||||
fn default() -> Self {
|
||||
ProposalState::Draft
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
//! Program accounts
|
||||
|
||||
pub mod enums;
|
||||
pub mod program_governance;
|
||||
pub mod proposal;
|
||||
pub mod proposal_vote_record;
|
||||
pub mod realm;
|
||||
pub mod single_signer_instruction;
|
||||
pub mod voter_record;
|
|
@ -0,0 +1,33 @@
|
|||
//! Program Governance Account
|
||||
|
||||
use solana_program::pubkey::Pubkey;
|
||||
|
||||
use super::enums::GovernanceAccountType;
|
||||
|
||||
/// Program Governance Account
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
pub struct ProgramGovernance {
|
||||
/// Account type
|
||||
pub account_type: GovernanceAccountType,
|
||||
|
||||
/// Voting threshold in % required to tip the vote
|
||||
/// It's the percentage of tokens out of the entire pool of governance tokens eligible to vote
|
||||
pub vote_threshold: u8,
|
||||
|
||||
/// Minimum % of tokens for a governance token owner to be able to create a proposal
|
||||
/// It's the percentage of tokens out of the entire pool of governance tokens eligible to vote
|
||||
pub token_threshold_to_create_proposal: u8,
|
||||
|
||||
/// Minimum waiting time in slots for an instruction to be executed after proposal is voted on
|
||||
pub min_instruction_hold_up_time: u64,
|
||||
|
||||
/// Program ID that is governed by this Governance
|
||||
pub program: Pubkey,
|
||||
|
||||
/// Time limit in slots for proposal to be open for voting
|
||||
pub max_voting_time: u64,
|
||||
|
||||
/// Running count of proposals
|
||||
pub proposal_count: u32,
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
//! Proposal Account
|
||||
|
||||
use solana_program::{epoch_schedule::Slot, pubkey::Pubkey};
|
||||
|
||||
use super::enums::{GovernanceAccountType, GoverningTokenType, ProposalState};
|
||||
|
||||
/// Governance Proposal
|
||||
#[repr(C)]
|
||||
#[derive(Clone)]
|
||||
pub struct Proposal {
|
||||
/// Governance account type
|
||||
pub account_type: GovernanceAccountType,
|
||||
|
||||
/// Governance account the Proposal belongs to
|
||||
pub governance: Pubkey,
|
||||
|
||||
/// Mint that creates signatory tokens of this Proposal
|
||||
/// If there are outstanding signatory tokens, then cannot leave draft state. Signatories must burn tokens (ie agree
|
||||
/// to move instruction to voting state) and bring mint to net 0 tokens outstanding. Each signatory gets 1 (serves as flag)
|
||||
pub signatory_mint: Pubkey,
|
||||
|
||||
/// Admin ownership mint. One token is minted, can be used to grant admin status to a new person
|
||||
pub admin_mint: Pubkey,
|
||||
|
||||
/// Indicates which Governing Token is used to vote on the Proposal
|
||||
/// Whether the general Community token owners or the Council tokens owners vote on this Proposal
|
||||
pub voting_token_type: GoverningTokenType,
|
||||
|
||||
/// Current state of the proposal
|
||||
pub state: ProposalState,
|
||||
|
||||
/// Total signatory tokens minted, for use comparing to supply remaining during draft period
|
||||
pub total_signatory_tokens_minted: u64,
|
||||
|
||||
/// Link to proposal's description
|
||||
pub description_link: String,
|
||||
|
||||
/// Proposal name
|
||||
pub name: String,
|
||||
|
||||
/// When the Proposal ended voting - this will also be when the set was defeated or began executing naturally
|
||||
pub voting_ended_at: Option<Slot>,
|
||||
|
||||
/// When the Proposal began voting
|
||||
pub voting_began_at: Option<Slot>,
|
||||
|
||||
/// when the Proposal entered draft state
|
||||
pub created_at: Option<Slot>,
|
||||
|
||||
/// when the Proposal entered completed state, also when execution ended naturally.
|
||||
pub completed_at: Option<Slot>,
|
||||
|
||||
/// when the Proposal entered deleted state
|
||||
pub deleted_at: Option<Slot>,
|
||||
|
||||
/// The number of the instructions already executed
|
||||
pub number_of_executed_instructions: u8,
|
||||
|
||||
/// The number of instructions included in the proposal
|
||||
pub number_of_instructions: u8,
|
||||
|
||||
/// Array of pubkeys pointing at Proposal instructions, up to 5
|
||||
pub instruction: Vec<Pubkey>,
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
//! Proposal Vote Record Account
|
||||
|
||||
use solana_program::pubkey::Pubkey;
|
||||
|
||||
use super::enums::{GovernanceAccountType, VoteWeight};
|
||||
|
||||
/// Proposal Vote Record
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
pub struct ProposalVoteRecord {
|
||||
/// Governance account type
|
||||
pub account_type: GovernanceAccountType,
|
||||
|
||||
/// Proposal account
|
||||
pub proposal: Pubkey,
|
||||
|
||||
/// The user who casted this vote
|
||||
/// This is the Governing Token Owner who deposited governing tokens into the Realm
|
||||
pub governing_token_owner: Pubkey,
|
||||
|
||||
/// Voter's vote: Yes/No and amount
|
||||
pub vote: Option<VoteWeight>,
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
//! Realm Account
|
||||
|
||||
use solana_program::pubkey::Pubkey;
|
||||
|
||||
use super::enums::GovernanceAccountType;
|
||||
|
||||
/// Governance Realm Account
|
||||
/// Account PDA seeds" ['governance', name]
|
||||
#[repr(C)]
|
||||
pub struct Realm {
|
||||
/// Governance account type
|
||||
pub account_type: GovernanceAccountType,
|
||||
|
||||
/// Community mint
|
||||
pub community_mint: Pubkey,
|
||||
|
||||
/// Council mint
|
||||
pub council_mint: Option<Pubkey>,
|
||||
|
||||
/// Governance Realm name
|
||||
pub name: String,
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
//! SingleSignerInstruction Account
|
||||
|
||||
use solana_program::instruction::Instruction;
|
||||
|
||||
use super::enums::GovernanceAccountType;
|
||||
|
||||
/// Account for an instruction to be executed for Proposal
|
||||
#[repr(C)]
|
||||
#[derive(Clone)]
|
||||
pub struct SingleSignerInstruction {
|
||||
/// Governance Account type
|
||||
pub account_type: GovernanceAccountType,
|
||||
|
||||
/// Minimum waiting time in slots for the instruction to be executed once proposal is voted on
|
||||
pub hold_up_time: u64,
|
||||
|
||||
/// Instruction to execute
|
||||
/// The instruction will be signed by Governance PDA the Proposal belongs to
|
||||
// For example for ProgramGovernance the instruction to upgrade program will be signed by ProgramGovernance PDA
|
||||
pub instruction: Instruction,
|
||||
|
||||
/// Executed flag
|
||||
pub executed: bool,
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
//! Voter Record Account
|
||||
|
||||
use solana_program::pubkey::Pubkey;
|
||||
|
||||
use super::enums::{GovernanceAccountType, GoverningTokenType};
|
||||
|
||||
/// Governance Voter Record
|
||||
/// Account PDA seeds: ['governance', realm, token_mint, token_owner ]
|
||||
#[repr(C)]
|
||||
pub struct VoterRecord {
|
||||
/// Governance account type
|
||||
pub account_type: GovernanceAccountType,
|
||||
|
||||
/// The Realm the VoterRecord belongs to
|
||||
pub realm: Pubkey,
|
||||
|
||||
/// The type of the Governing Token the VoteRecord is for
|
||||
pub token_type: GoverningTokenType,
|
||||
|
||||
/// The owner (either single or multisig) of the deposited governing SPL Tokens
|
||||
/// This is who can authorize a withdrawal
|
||||
pub token_owner: Pubkey,
|
||||
|
||||
/// The amount of governing tokens deposited into the Realm
|
||||
/// This amount is the voter weight used when voting on proposals
|
||||
pub token_deposit_amount: u64,
|
||||
|
||||
/// A single account that is allowed to operate governance with the deposited governing tokens
|
||||
/// It's delegated to by the token owner
|
||||
pub vote_authority: Pubkey,
|
||||
|
||||
/// The number of active votes cast by voter
|
||||
pub active_votes_count: u8,
|
||||
|
||||
/// The total number of votes cast by the voter
|
||||
pub total_votes_count: u8,
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 203 KiB |
Binary file not shown.
After Width: | Height: | Size: 225 KiB |
Loading…
Reference in New Issue