Skip to content

Commit e5af52d

Browse files
SebastianBormvines
andauthored
Governance program API design
Co-authored-by: Michael Vines <mvines@gmail.com>
1 parent 1beeb9f commit e5af52d

21 files changed

+772
-0
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ hfuzz_target
99
hfuzz_workspace
1010
**/*.so
1111
**/.DS_Store
12+
test-ledger

Cargo.lock

+13
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ members = [
99
"examples/rust/transfer-lamports",
1010
"feature-proposal/program",
1111
"feature-proposal/cli",
12+
"governance/program",
1213
"libraries/math",
1314
"memo/program",
1415
"name-service/program",

governance/README.md

+96
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
# Governance
2+
3+
Governance is a program the chief purpose of which is to control the upgrade of other programs through democratic means.
4+
It can also be used as an authority provider for mints and other forms of access control as well where we may want
5+
a voting population to vote on disbursement of access or funds collectively.
6+
7+
## Architecture
8+
9+
### Accounts diagram
10+
11+
![Accounts diagram](./resources/governance-accounts.jpg)
12+
13+
### Governance Realm account
14+
15+
Governance Realm ties Community Token Mint and optional Council Token mint to create a realm
16+
for any governance pertaining to the community of the token holders.
17+
For example a trading protocol can issue a governance token and use it to create its governance realm.
18+
19+
Once a realm is created voters can deposit Governing tokens (Community or Council) to the realm and
20+
use the deposited amount as their voting weight to vote on Proposals within that realm.
21+
22+
### Program Governance account
23+
24+
The basic building block of governance to update programs is the ProgramGovernance account.
25+
It ties a governed Program ID and holds configuration options defining governance rules.
26+
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),
27+
and this program derived address is what is used as the address of the Governance account for your Program ID
28+
and the corresponding Governance mint and Council mint (if provided).
29+
30+
What this means is that there can only ever be ONE Governance account for a given Program.
31+
The governance program validates at creation time of the Governance account that the current upgrade authority of the program
32+
taken under governance signed the transaction.
33+
34+
Note: In future versions, once allowed in solana runtime, the governance program will take over the upgrade authority
35+
of the governed program when the Governance account is created.
36+
37+
### How does authority work?
38+
39+
Governance can handle arbitrary executions of code, but it's real power lies in the power to upgrade programs.
40+
It does this through executing commands to the bpf-upgradable-loader program.
41+
Bpf-upgradable-loader allows any signer who has Upgrade authority over a Buffer account and the Program account itself
42+
to upgrade it using its Upgrade command.
43+
Normally, this is the developer who created and deployed the program, and this creation of the Buffer account containing
44+
the new program data and overwriting of the existing Program account's data with it is handled in the background for you
45+
by the Solana program deploy cli command.
46+
However, in order for Governance to be useful, Governance now needs this authority.
47+
48+
### Proposal accounts
49+
50+
A Proposal is an instance of a Governance created to vote on and execute given set of changes.
51+
It is created by someone (Proposal Admin) and tied to a given Governance account
52+
and has a set of executable commands to it, a name and a description.
53+
It goes through various states (draft, voting, executing) and users can vote on it
54+
if they have relevant Community or Council tokens.
55+
It's rules are determined by the Governance account that it is tied to, and when it executes,
56+
it is only eligible to use the [Program Derived Address](https://docs.solana.com/developing/programming-model/calling-between-programs#program-derived-addresses)
57+
authority given by the Governance account.
58+
So a Proposal for Sushi cannot for instance upgrade the Program for Uniswap.
59+
60+
When a Proposal is created by a user then the user becomes Proposal Admin and receives an Admin an Signatory token.
61+
With this power the Admin can add other Signatories to the Proposal.
62+
These Signatories can then add commands to the Proposal and/or sign off on the Proposal.
63+
Once all Signatories have signed off on the Proposal the Proposal leaves Draft state and enters Voting state.
64+
Voting state lasts as long as the Governance has it configured to last, and during this time
65+
people holding Community (or Council) tokens may vote on the Proposal.
66+
Once the Proposal is "tipped" it either enters the Defeated or Executing state.
67+
If Executed, it enters Completed state once all commands have been run.
68+
69+
A command can be run by any one at any time after the `instruction_hold_up_time` length has transpired on the given command.
70+
71+
### SingleSignerInstruction
72+
73+
We only support one kind of executable command right now, and this is the `SingleSignerInstruction` type.
74+
A Proposal can have a certain number of these, and they run independently of each other.
75+
These contain the actual data for a command, and how long after the voting phase a user must wait before they can be executed.
76+
77+
### Voting Dynamics
78+
79+
When a Proposal is created and signed by its Signatories voters can start voting on it using their voting weight,
80+
equal to deposited governing tokens into the realm. A vote is tipped once it passes the defined `vote_threshold` of votes
81+
and enters Succeeded or Defeated state. If Succeeded then Proposal instructions can be executed after they hold_up_time passes.
82+
83+
Users can relinquish their vote any time during Proposal lifetime, but once Proposal it tipped their vote can't be changed.
84+
85+
### Community and Councils governing tokens
86+
87+
Each Governance Realm that gets created has the option to also have a Council mint.
88+
A council mint is simply a separate mint from the Community mint.
89+
What this means is that users can submit Proposals that have a different voting population from a different mint
90+
that can affect the same program. A practical application of this policy may be to have a very large population control
91+
major version bumps of Solana via normal SOL, for instance, but hot fixes be controlled via Council tokens,
92+
of which there may be only 30, and which may be themselves minted and distributed via proposals by the governing population.
93+
94+
### Proposal Workflow
95+
96+
![Proposal Workflow](./resources/governance-workflow.jpg)

governance/program/Cargo.toml

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
[package]
2+
name = "spl-governance"
3+
version = "0.1.0"
4+
description = "Solana Program Library Governance"
5+
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
6+
repository = "https://github.com/solana-labs/solana-program-library"
7+
license = "Apache-2.0"
8+
edition = "2018"
9+
10+
11+
[features]
12+
no-entrypoint = []
13+
test-bpf = []
14+
15+
16+
[dependencies]
17+
solana-program = "1.6.7"
18+
thiserror = "1.0"
19+
num-derive = "0.3"
20+
num-traits = "0.2"
21+
22+
[dev-dependencies]
23+
assert_matches = "1.5.0"
24+
solana-program-test = "1.6.7"
25+
solana-sdk = "1.6.7"
26+
27+
28+
[lib]
29+
crate-type = ["cdylib", "lib"]

governance/program/Xargo.toml

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[target.bpfel-unknown-unknown.dependencies.std]
2+
features = []

governance/program/src/entrypoint.rs

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
//! Program entrypoint definitions
2+
#![cfg(all(target_arch = "bpf", not(feature = "no-entrypoint")))]
3+
4+
use crate::{error::GovernanceError, processor};
5+
use solana_program::{
6+
account_info::AccountInfo, entrypoint, entrypoint::ProgramResult,
7+
program_error::PrintProgramError, pubkey::Pubkey,
8+
};
9+
10+
entrypoint!(process_instruction);
11+
fn process_instruction(
12+
program_id: &Pubkey,
13+
accounts: &[AccountInfo],
14+
instruction_data: &[u8],
15+
) -> ProgramResult {
16+
if let Err(error) = processor::process_instruction(program_id, accounts, instruction_data) {
17+
// catch the error so we can print it
18+
error.print::<GovernanceError>();
19+
return Err(error);
20+
}
21+
Ok(())
22+
}

governance/program/src/error.rs

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
//! Error types
2+
3+
use num_derive::FromPrimitive;
4+
use solana_program::{
5+
decode_error::DecodeError,
6+
msg,
7+
program_error::{PrintProgramError, ProgramError},
8+
};
9+
use thiserror::Error;
10+
11+
/// Errors that may be returned by the Governance program.
12+
#[derive(Clone, Debug, Eq, Error, FromPrimitive, PartialEq)]
13+
pub enum GovernanceError {
14+
/// Invalid instruction passed to program.
15+
#[error("Invalid instruction passed to program")]
16+
InvalidInstruction,
17+
}
18+
19+
impl PrintProgramError for GovernanceError {
20+
fn print<E>(&self) {
21+
msg!(&self.to_string());
22+
}
23+
}
24+
25+
impl From<GovernanceError> for ProgramError {
26+
fn from(e: GovernanceError) -> Self {
27+
ProgramError::Custom(e as u32)
28+
}
29+
}
30+
31+
impl<T> DecodeError<T> for GovernanceError {
32+
fn type_of() -> &'static str {
33+
"Governance Error"
34+
}
35+
}

0 commit comments

Comments
 (0)