Module 8: DApp 4 - DAO Governance¶
Learning Objectives
By the end of this module, you will:
- Understand DAO fundamentals and governance architecture
- Implement proposal creation and lifecycle management
- Build voting mechanisms with token-weighted voting
- Design treasury management with multi-sig patterns
- Apply delegation and quorum requirements
Prerequisites¶
Complete Module 7: DeFi AMM first.
8.1 DAO Fundamentals¶
What is a DAO?¶
A Decentralized Autonomous Organization (DAO) is a blockchain-based governance system where:
- Proposals: Members submit suggestions for changes
- Voting: Token holders vote on proposals
- Execution: Approved proposals are automatically executed
- Treasury: Collective funds are managed by the DAO
Traditional vs DAO Governance¶
Traditional Organization DAO
┌─────────────────────┐ ┌─────────────────────┐
│ Board of │ │ Token Holders │
│ Directors │ │ (Members) │
├─────────────────────┤ ├─────────────────────┤
│ - Centralized │ │ - Decentralized │
│ - Closed meetings │ │ - On-chain voting │
│ - Trust required │ │ - Trustless │
│ - Manual execution │ │ - Auto-execution │
└─────────────────────┘ └─────────────────────┘
| Aspect | Traditional | DAO |
|---|---|---|
| Decision Making | Board/Executives | Token holders |
| Transparency | Limited | Full on-chain |
| Execution | Manual | Smart contract |
| Participation | Shareholders only | Anyone with tokens |
| Trust Model | Trust leadership | Trust code |
Key Components¶
- Governance Token: Voting power representation
- Proposal System: Structured change requests
- Voting Mechanism: How votes are counted
- Timelock: Delay before execution
- Treasury: Collective asset management
8.2 Governance Architecture¶
System Overview¶
┌─────────────────────────────────────────────────────────────┐
│ Governance System │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Governance │ │ Proposal │ │ Vote │ │
│ │ Account │───▶│ Account │◀───│ Record │ │
│ │ │ │ │ │ │ │
│ │ - token_mint │ │ - proposer │ │ - voter │ │
│ │ - quorum │ │ - votes_for │ │ - weight │ │
│ │ - voting_pd │ │ - votes_agst │ │ - support │ │
│ └──────────────┘ │ - state │ └──────────────┘ │
│ │ └──────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Treasury │ │ Timelock │ │
│ │ (PDA) │◀───│ Queue │ │
│ │ │ │ │ │
│ │ Holds SOL + │ │ Pending │ │
│ │ Tokens │ │ executions │ │
│ └──────────────┘ └──────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
Governance Account¶
use anchor_lang::prelude::*;
#[account]
#[derive(InitSpace)]
pub struct Governance {
/// Authority who can update governance parameters
pub authority: Pubkey,
/// Governance token mint (voting power)
pub governance_token_mint: Pubkey,
/// Treasury account holding DAO funds
pub treasury: Pubkey,
/// Total proposals created
pub proposal_count: u64,
/// Voting period in seconds
pub voting_period: i64,
/// Minimum tokens required to create a proposal
pub proposal_threshold: u64,
/// Minimum votes needed for proposal to pass (quorum)
pub quorum_votes: u64,
/// Delay after voting before execution (seconds)
pub execution_delay: i64,
/// PDA bump
pub bump: u8,
/// Treasury bump
pub treasury_bump: u8,
}
Proposal Account¶
#[account]
#[derive(InitSpace)]
pub struct Proposal {
/// Reference to governance
pub governance: Pubkey,
/// Who created the proposal
pub proposer: Pubkey,
/// Unique proposal ID
pub proposal_id: u64,
/// Short title (max 64 chars)
#[max_len(64)]
pub title: String,
/// Description URI (IPFS hash or URL)
#[max_len(128)]
pub description_uri: String,
/// Total votes in favor
pub votes_for: u64,
/// Total votes against
pub votes_against: u64,
/// When voting starts (unix timestamp)
pub voting_starts_at: i64,
/// When voting ends (unix timestamp)
pub voting_ends_at: i64,
/// Timestamp when proposal can be executed
pub execution_time: i64,
/// Current proposal state
pub state: ProposalState,
/// PDA bump
pub bump: u8,
}
#[derive(AnchorSerialize, AnchorDeserialize, Clone, PartialEq, Eq, InitSpace)]
pub enum ProposalState {
/// Proposal created, not yet active
Draft,
/// Voting is active
Active,
/// Voting ended, passed quorum
Succeeded,
/// Voting ended, did not pass
Defeated,
/// Queued for execution after timelock
Queued,
/// Successfully executed
Executed,
/// Cancelled by proposer
Cancelled,
}
Vote Record¶
#[account]
#[derive(InitSpace)]
pub struct VoteRecord {
/// Reference to proposal
pub proposal: Pubkey,
/// Voter's pubkey
pub voter: Pubkey,
/// Vote weight (token balance at snapshot)
pub vote_weight: u64,
/// true = for, false = against
pub support: bool,
/// When vote was cast
pub voted_at: i64,
/// PDA bump
pub bump: u8,
}
8.3 Proposal Lifecycle¶
State Machine¶
┌───────────┐
│ Draft │──────────────────────┐
└─────┬─────┘ │
│ activate() │ cancel()
▼ │
┌───────────┐ │
│ Active │──────────────────────┤
└─────┬─────┘ │
│ voting period ends │
▼ │
┌─────────────────────┐ │
│ finalize_votes() │ │
├─────────┬───────────┤ │
│ │ │ │
▼ ▼ │ │
┌─────┐ ┌────────┐ │ │
│Succ-│ │Defeated│ │ │
│eeded│ └────────┘ │ │
└──┬──┘ │ │
│ queue() │ │
▼ │ ▼
┌──────┐ │ ┌──────────┐
│Queued│──────────────┴─────▶│Cancelled │
└──┬───┘ cancel() └──────────┘
│ execute() after delay
▼
┌────────┐
│Executed│
└────────┘
Proposal Thresholds¶
| Parameter | Description | Example |
|---|---|---|
| Proposal Threshold | Min tokens to create proposal | 100,000 tokens |
| Quorum | Min votes for valid result | 4% of supply |
| Voting Period | How long voting is open | 3 days |
| Execution Delay | Time after success before execute | 2 days |
8.4 Voting Mechanisms¶
Token-Weighted Voting¶
The simplest voting mechanism where vote weight equals token balance:
\[\text{Vote Weight} = \text{Token Balance}\]
pub fn cast_vote(
ctx: Context<CastVote>,
support: bool,
) -> Result<()> {
let proposal = &mut ctx.accounts.proposal;
let voter_tokens = ctx.accounts.voter_token_account.amount;
require!(voter_tokens > 0, GovernanceError::NoVotingPower);
// Record vote weight from token balance
let vote_record = &mut ctx.accounts.vote_record;
vote_record.vote_weight = voter_tokens;
vote_record.support = support;
// Add to proposal totals
if support {
proposal.votes_for = proposal.votes_for
.checked_add(voter_tokens)
.ok_or(GovernanceError::Overflow)?;
} else {
proposal.votes_against = proposal.votes_against
.checked_add(voter_tokens)
.ok_or(GovernanceError::Overflow)?;
}
Ok(())
}
Quadratic Voting (Advanced)¶
Reduces whale dominance by taking square root of token balance:
\[\text{Vote Weight} = \sqrt{\text{Token Balance}}\]
pub fn calculate_quadratic_weight(token_balance: u64) -> u64 {
// Integer square root
let balance = token_balance as u128;
let mut x = balance;
let mut y = (x + 1) / 2;
while y < x {
x = y;
y = (x + balance / x) / 2;
}
x as u64
}
Comparison:
| Token Balance | Linear Weight | Quadratic Weight |
|---|---|---|
| 100 | 100 | 10 |
| 10,000 | 10,000 | 100 |
| 1,000,000 | 1,000,000 | 1,000 |
Vote Delegation¶
Allow token holders to delegate voting power:
#[account]
#[derive(InitSpace)]
pub struct Delegation {
/// Who is delegating
pub delegator: Pubkey,
/// Who receives the voting power
pub delegate: Pubkey,
/// Amount delegated
pub amount: u64,
/// When delegation was created
pub created_at: i64,
pub bump: u8,
}
pub fn delegate_votes(
ctx: Context<DelegateVotes>,
amount: u64,
) -> Result<()> {
let delegation = &mut ctx.accounts.delegation;
delegation.delegator = ctx.accounts.delegator.key();
delegation.delegate = ctx.accounts.delegate.key();
delegation.amount = amount;
delegation.created_at = Clock::get()?.unix_timestamp;
emit!(VotesDelegated {
delegator: delegation.delegator,
delegate: delegation.delegate,
amount,
});
Ok(())
}
8.5 Treasury Management¶
Treasury PDA¶
The treasury is a Program Derived Address controlled by the governance:
#[derive(Accounts)]
pub struct InitializeGovernance<'info> {
#[account(
init,
payer = authority,
space = 8 + Governance::INIT_SPACE,
seeds = [b"governance", authority.key().as_ref()],
bump,
)]
pub governance: Account<'info, Governance>,
/// Treasury PDA - holds SOL and can hold token accounts
#[account(
seeds = [b"treasury", governance.key().as_ref()],
bump,
)]
pub treasury: SystemAccount<'info>,
#[account(mut)]
pub authority: Signer<'info>,
pub system_program: Program<'info, System>,
}
Treasury Operations¶
Proposals can execute treasury transfers:
#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
pub enum ProposalInstruction {
/// Transfer SOL from treasury
TransferSol {
recipient: Pubkey,
amount: u64,
},
/// Transfer tokens from treasury
TransferToken {
recipient: Pubkey,
mint: Pubkey,
amount: u64,
},
/// Update governance parameters
UpdateGovernance {
voting_period: Option<i64>,
quorum_votes: Option<u64>,
execution_delay: Option<i64>,
},
}
Executing Treasury Transfer¶
pub fn execute_proposal(ctx: Context<ExecuteProposal>) -> Result<()> {
let proposal = &mut ctx.accounts.proposal;
let governance = &ctx.accounts.governance;
// Verify proposal is queued and delay has passed
require!(
proposal.state == ProposalState::Queued,
GovernanceError::ProposalNotQueued
);
let now = Clock::get()?.unix_timestamp;
require!(
now >= proposal.execution_time,
GovernanceError::ExecutionDelayNotPassed
);
// Execute treasury transfer using governance PDA as signer
let governance_key = governance.key();
let seeds = &[
b"treasury",
governance_key.as_ref(),
&[governance.treasury_bump],
];
let signer_seeds = &[&seeds[..]];
// Example: Transfer SOL
anchor_lang::system_program::transfer(
CpiContext::new_with_signer(
ctx.accounts.system_program.to_account_info(),
anchor_lang::system_program::Transfer {
from: ctx.accounts.treasury.to_account_info(),
to: ctx.accounts.recipient.to_account_info(),
},
signer_seeds,
),
proposal.transfer_amount,
)?;
proposal.state = ProposalState::Executed;
emit!(ProposalExecuted {
proposal: proposal.key(),
executor: ctx.accounts.executor.key(),
});
Ok(())
}
8.6 Complete Implementation¶
Initialize Governance¶
use anchor_lang::prelude::*;
#[derive(Accounts)]
pub struct InitializeGovernance<'info> {
#[account(
init,
payer = authority,
space = 8 + Governance::INIT_SPACE,
seeds = [b"governance", authority.key().as_ref()],
bump,
)]
pub governance: Account<'info, Governance>,
#[account(
seeds = [b"treasury", governance.key().as_ref()],
bump,
)]
pub treasury: SystemAccount<'info>,
/// Governance token mint
pub governance_token_mint: Account<'info, Mint>,
#[account(mut)]
pub authority: Signer<'info>,
pub system_program: Program<'info, System>,
}
pub fn handler(
ctx: Context<InitializeGovernance>,
voting_period: i64,
proposal_threshold: u64,
quorum_votes: u64,
execution_delay: i64,
) -> Result<()> {
require!(voting_period > 0, GovernanceError::InvalidVotingPeriod);
require!(execution_delay >= 0, GovernanceError::InvalidExecutionDelay);
let governance = &mut ctx.accounts.governance;
governance.authority = ctx.accounts.authority.key();
governance.governance_token_mint = ctx.accounts.governance_token_mint.key();
governance.treasury = ctx.accounts.treasury.key();
governance.proposal_count = 0;
governance.voting_period = voting_period;
governance.proposal_threshold = proposal_threshold;
governance.quorum_votes = quorum_votes;
governance.execution_delay = execution_delay;
governance.bump = ctx.bumps.governance;
governance.treasury_bump = ctx.bumps.treasury;
emit!(GovernanceInitialized {
governance: governance.key(),
authority: governance.authority,
voting_period,
quorum_votes,
});
Ok(())
}
Create Proposal¶
#[derive(Accounts)]
#[instruction(title: String, description_uri: String)]
pub struct CreateProposal<'info> {
#[account(
mut,
seeds = [b"governance", governance.authority.as_ref()],
bump = governance.bump,
)]
pub governance: Account<'info, Governance>,
#[account(
init,
payer = proposer,
space = 8 + Proposal::INIT_SPACE,
seeds = [
b"proposal",
governance.key().as_ref(),
&governance.proposal_count.to_le_bytes()
],
bump,
)]
pub proposal: Account<'info, Proposal>,
#[account(
constraint = proposer_token_account.mint == governance.governance_token_mint,
constraint = proposer_token_account.owner == proposer.key(),
constraint = proposer_token_account.amount >= governance.proposal_threshold
@ GovernanceError::InsufficientTokensForProposal,
)]
pub proposer_token_account: Account<'info, TokenAccount>,
#[account(mut)]
pub proposer: Signer<'info>,
pub system_program: Program<'info, System>,
}
pub fn handler(
ctx: Context<CreateProposal>,
title: String,
description_uri: String,
) -> Result<()> {
let governance = &mut ctx.accounts.governance;
let proposal = &mut ctx.accounts.proposal;
let now = Clock::get()?.unix_timestamp;
proposal.governance = governance.key();
proposal.proposer = ctx.accounts.proposer.key();
proposal.proposal_id = governance.proposal_count;
proposal.title = title;
proposal.description_uri = description_uri;
proposal.votes_for = 0;
proposal.votes_against = 0;
proposal.voting_starts_at = now;
proposal.voting_ends_at = now + governance.voting_period;
proposal.execution_time = 0;
proposal.state = ProposalState::Active;
proposal.bump = ctx.bumps.proposal;
governance.proposal_count = governance.proposal_count
.checked_add(1)
.ok_or(GovernanceError::Overflow)?;
emit!(ProposalCreated {
proposal: proposal.key(),
governance: governance.key(),
proposer: proposal.proposer,
proposal_id: proposal.proposal_id,
title: proposal.title.clone(),
});
Ok(())
}
Cast Vote¶
#[derive(Accounts)]
pub struct CastVote<'info> {
#[account(
seeds = [b"governance", governance.authority.as_ref()],
bump = governance.bump,
)]
pub governance: Account<'info, Governance>,
#[account(
mut,
seeds = [
b"proposal",
governance.key().as_ref(),
&proposal.proposal_id.to_le_bytes()
],
bump = proposal.bump,
constraint = proposal.state == ProposalState::Active @ GovernanceError::ProposalNotActive,
)]
pub proposal: Account<'info, Proposal>,
#[account(
init,
payer = voter,
space = 8 + VoteRecord::INIT_SPACE,
seeds = [b"vote", proposal.key().as_ref(), voter.key().as_ref()],
bump,
)]
pub vote_record: Account<'info, VoteRecord>,
#[account(
constraint = voter_token_account.mint == governance.governance_token_mint,
constraint = voter_token_account.owner == voter.key(),
)]
pub voter_token_account: Account<'info, TokenAccount>,
#[account(mut)]
pub voter: Signer<'info>,
pub system_program: Program<'info, System>,
}
pub fn handler(ctx: Context<CastVote>, support: bool) -> Result<()> {
let proposal = &mut ctx.accounts.proposal;
let vote_record = &mut ctx.accounts.vote_record;
let voter_tokens = ctx.accounts.voter_token_account.amount;
let now = Clock::get()?.unix_timestamp;
// Check voting period
require!(
now >= proposal.voting_starts_at && now <= proposal.voting_ends_at,
GovernanceError::VotingPeriodEnded
);
require!(voter_tokens > 0, GovernanceError::NoVotingPower);
// Record vote
vote_record.proposal = proposal.key();
vote_record.voter = ctx.accounts.voter.key();
vote_record.vote_weight = voter_tokens;
vote_record.support = support;
vote_record.voted_at = now;
vote_record.bump = ctx.bumps.vote_record;
// Update proposal totals
if support {
proposal.votes_for = proposal.votes_for
.checked_add(voter_tokens)
.ok_or(GovernanceError::Overflow)?;
} else {
proposal.votes_against = proposal.votes_against
.checked_add(voter_tokens)
.ok_or(GovernanceError::Overflow)?;
}
emit!(VoteCast {
proposal: proposal.key(),
voter: vote_record.voter,
support,
weight: voter_tokens,
});
Ok(())
}
Finalize Proposal¶
#[derive(Accounts)]
pub struct FinalizeProposal<'info> {
#[account(
seeds = [b"governance", governance.authority.as_ref()],
bump = governance.bump,
)]
pub governance: Account<'info, Governance>,
#[account(
mut,
seeds = [
b"proposal",
governance.key().as_ref(),
&proposal.proposal_id.to_le_bytes()
],
bump = proposal.bump,
constraint = proposal.state == ProposalState::Active @ GovernanceError::ProposalNotActive,
)]
pub proposal: Account<'info, Proposal>,
}
pub fn handler(ctx: Context<FinalizeProposal>) -> Result<()> {
let governance = &ctx.accounts.governance;
let proposal = &mut ctx.accounts.proposal;
let now = Clock::get()?.unix_timestamp;
// Check voting period has ended
require!(
now > proposal.voting_ends_at,
GovernanceError::VotingPeriodNotEnded
);
let total_votes = proposal.votes_for
.checked_add(proposal.votes_against)
.ok_or(GovernanceError::Overflow)?;
// Check quorum
if total_votes < governance.quorum_votes {
proposal.state = ProposalState::Defeated;
emit!(ProposalDefeated {
proposal: proposal.key(),
reason: "Quorum not reached".to_string(),
});
return Ok(());
}
// Check if passed (simple majority)
if proposal.votes_for > proposal.votes_against {
proposal.state = ProposalState::Succeeded;
emit!(ProposalSucceeded {
proposal: proposal.key(),
votes_for: proposal.votes_for,
votes_against: proposal.votes_against,
});
} else {
proposal.state = ProposalState::Defeated;
emit!(ProposalDefeated {
proposal: proposal.key(),
reason: "Did not reach majority".to_string(),
});
}
Ok(())
}
Queue Proposal¶
#[derive(Accounts)]
pub struct QueueProposal<'info> {
#[account(
seeds = [b"governance", governance.authority.as_ref()],
bump = governance.bump,
)]
pub governance: Account<'info, Governance>,
#[account(
mut,
seeds = [
b"proposal",
governance.key().as_ref(),
&proposal.proposal_id.to_le_bytes()
],
bump = proposal.bump,
constraint = proposal.state == ProposalState::Succeeded @ GovernanceError::ProposalNotSucceeded,
)]
pub proposal: Account<'info, Proposal>,
}
pub fn handler(ctx: Context<QueueProposal>) -> Result<()> {
let governance = &ctx.accounts.governance;
let proposal = &mut ctx.accounts.proposal;
let now = Clock::get()?.unix_timestamp;
proposal.execution_time = now + governance.execution_delay;
proposal.state = ProposalState::Queued;
emit!(ProposalQueued {
proposal: proposal.key(),
execution_time: proposal.execution_time,
});
Ok(())
}
Execute Proposal¶
#[derive(Accounts)]
pub struct ExecuteProposal<'info> {
#[account(
seeds = [b"governance", governance.authority.as_ref()],
bump = governance.bump,
)]
pub governance: Account<'info, Governance>,
#[account(
mut,
seeds = [b"treasury", governance.key().as_ref()],
bump = governance.treasury_bump,
)]
pub treasury: SystemAccount<'info>,
#[account(
mut,
seeds = [
b"proposal",
governance.key().as_ref(),
&proposal.proposal_id.to_le_bytes()
],
bump = proposal.bump,
constraint = proposal.state == ProposalState::Queued @ GovernanceError::ProposalNotQueued,
)]
pub proposal: Account<'info, Proposal>,
/// CHECK: Validated by proposal data
#[account(mut)]
pub recipient: UncheckedAccount<'info>,
pub executor: Signer<'info>,
pub system_program: Program<'info, System>,
}
pub fn handler(ctx: Context<ExecuteProposal>, transfer_amount: u64) -> Result<()> {
let governance = &ctx.accounts.governance;
let proposal = &mut ctx.accounts.proposal;
let now = Clock::get()?.unix_timestamp;
// Check execution delay has passed
require!(
now >= proposal.execution_time,
GovernanceError::ExecutionDelayNotPassed
);
// Execute transfer from treasury
let governance_key = governance.key();
let seeds = &[
b"treasury",
governance_key.as_ref(),
&[governance.treasury_bump],
];
let signer_seeds = &[&seeds[..]];
anchor_lang::system_program::transfer(
CpiContext::new_with_signer(
ctx.accounts.system_program.to_account_info(),
anchor_lang::system_program::Transfer {
from: ctx.accounts.treasury.to_account_info(),
to: ctx.accounts.recipient.to_account_info(),
},
signer_seeds,
),
transfer_amount,
)?;
proposal.state = ProposalState::Executed;
emit!(ProposalExecuted {
proposal: proposal.key(),
executor: ctx.accounts.executor.key(),
transfer_amount,
});
Ok(())
}
8.7 Cancel Proposal¶
Proposers can cancel their own proposals:
#[derive(Accounts)]
pub struct CancelProposal<'info> {
#[account(
seeds = [b"governance", governance.authority.as_ref()],
bump = governance.bump,
)]
pub governance: Account<'info, Governance>,
#[account(
mut,
seeds = [
b"proposal",
governance.key().as_ref(),
&proposal.proposal_id.to_le_bytes()
],
bump = proposal.bump,
constraint = proposal.proposer == proposer.key() @ GovernanceError::NotProposer,
constraint = proposal.state != ProposalState::Executed @ GovernanceError::ProposalAlreadyExecuted,
constraint = proposal.state != ProposalState::Cancelled @ GovernanceError::ProposalAlreadyCancelled,
)]
pub proposal: Account<'info, Proposal>,
pub proposer: Signer<'info>,
}
pub fn handler(ctx: Context<CancelProposal>) -> Result<()> {
let proposal = &mut ctx.accounts.proposal;
proposal.state = ProposalState::Cancelled;
emit!(ProposalCancelled {
proposal: proposal.key(),
proposer: ctx.accounts.proposer.key(),
});
Ok(())
}
8.8 Security Considerations¶
Common Governance Vulnerabilities¶
| Vulnerability | Description | Mitigation |
|---|---|---|
| Flash loan attacks | Borrow tokens, vote, repay | Snapshot voting power |
| Vote buying | Off-chain bribes | Use quadratic voting |
| Proposal spam | Flood with proposals | Proposal threshold |
| Timelock bypass | Execute without delay | Strict state machine |
| Treasury drain | Malicious proposals | Multi-sig + limits |
Best Practices¶
- Snapshot Voting Power: Record token balance at proposal creation
- Minimum Voting Period: At least 24-72 hours for participation
- Execution Delay: 2-7 days to allow for emergency response
- Quorum Requirements: 4-10% of circulating supply
- Proposal Threshold: Prevent spam while allowing participation
- Emergency Pause: Guardian can pause execution in emergencies
Snapshot Implementation¶
#[account]
pub struct VotingPowerSnapshot {
pub voter: Pubkey,
pub proposal: Pubkey,
pub balance_at_snapshot: u64,
pub snapshot_slot: u64,
}
// Take snapshot when creating proposal
pub fn create_proposal_with_snapshot(
ctx: Context<CreateProposalWithSnapshot>,
title: String,
description_uri: String,
) -> Result<()> {
let proposal = &mut ctx.accounts.proposal;
proposal.snapshot_slot = Clock::get()?.slot;
// Voters must prove their balance at this slot
// ...
}
8.9 Program Structure¶
File Organization¶
programs/dao-governance/
├── Cargo.toml
├── src/
│ ├── lib.rs
│ ├── state.rs
│ ├── error.rs
│ ├── events.rs
│ └── instructions/
│ ├── mod.rs
│ ├── initialize_governance.rs
│ ├── create_proposal.rs
│ ├── cast_vote.rs
│ ├── finalize_proposal.rs
│ ├── queue_proposal.rs
│ ├── execute_proposal.rs
│ └── cancel_proposal.rs
Program Entry Point¶
// src/lib.rs
use anchor_lang::prelude::*;
pub mod error;
pub mod events;
pub mod instructions;
pub mod state;
use instructions::*;
declare_id!("Gov11111111111111111111111111111111111111111");
#[program]
pub mod dao_governance {
use super::*;
pub fn initialize_governance(
ctx: Context<InitializeGovernance>,
voting_period: i64,
proposal_threshold: u64,
quorum_votes: u64,
execution_delay: i64,
) -> Result<()> {
instructions::initialize_governance::handler(
ctx,
voting_period,
proposal_threshold,
quorum_votes,
execution_delay,
)
}
pub fn create_proposal(
ctx: Context<CreateProposal>,
title: String,
description_uri: String,
) -> Result<()> {
instructions::create_proposal::handler(ctx, title, description_uri)
}
pub fn cast_vote(ctx: Context<CastVote>, support: bool) -> Result<()> {
instructions::cast_vote::handler(ctx, support)
}
pub fn finalize_proposal(ctx: Context<FinalizeProposal>) -> Result<()> {
instructions::finalize_proposal::handler(ctx)
}
pub fn queue_proposal(ctx: Context<QueueProposal>) -> Result<()> {
instructions::queue_proposal::handler(ctx)
}
pub fn execute_proposal(
ctx: Context<ExecuteProposal>,
transfer_amount: u64,
) -> Result<()> {
instructions::execute_proposal::handler(ctx, transfer_amount)
}
pub fn cancel_proposal(ctx: Context<CancelProposal>) -> Result<()> {
instructions::cancel_proposal::handler(ctx)
}
}
8.10 Interactive Exercise¶
Try It: Build Your DAO
Open Solana Playground and implement:
- Initialize a governance with your parameters
- Create a proposal for treasury spending
- Cast votes for/against
- Finalize and execute the proposal
Test Scenarios¶
describe("dao-governance", () => {
it("creates and passes a proposal", async () => {
// Initialize governance
await program.methods
.initializeGovernance(
new anchor.BN(60 * 60), // 1 hour voting
new anchor.BN(100), // 100 tokens to propose
new anchor.BN(1000), // 1000 token quorum
new anchor.BN(60) // 1 minute delay
)
.accounts({ /* ... */ })
.rpc();
// Create proposal
await program.methods
.createProposal("Fund Development", "ipfs://...")
.accounts({ /* ... */ })
.rpc();
// Cast vote
await program.methods
.castVote(true) // Vote in favor
.accounts({ /* ... */ })
.rpc();
// Fast forward time in tests...
// Finalize
await program.methods
.finalizeProposal()
.accounts({ /* ... */ })
.rpc();
// Queue
await program.methods
.queueProposal()
.accounts({ /* ... */ })
.rpc();
// Execute
await program.methods
.executeProposal(new anchor.BN(LAMPORTS_PER_SOL))
.accounts({ /* ... */ })
.rpc();
});
});
Summary¶
In this module, you learned:
- DAO Fundamentals: Decentralized governance vs traditional organizations
- Governance Architecture: Governance, Proposal, and VoteRecord accounts
- Proposal Lifecycle: Draft -> Active -> Succeeded/Defeated -> Queued -> Executed
- Voting Mechanisms: Token-weighted and quadratic voting
- Treasury Management: PDA-controlled funds with proposal-gated access
- Security: Snapshots, timelock, quorum requirements
Next Steps¶
Continue to Module 9: Frontend Patterns to learn:
- React wallet adapter integration
- Transaction building with @solana/web3.js
- State management for DApps
- NextJS deployment