DAO Governance: Voting, Proposals, and Treasury
Implementing Decentralized Autonomous Organizations with voting mechanisms, proposals, and treasury management.
DAO Governance: Voting, Proposals, and Treasury
DAOs (Decentralized Autonomous Organizations) enable decentralized decision-making.
What is a DAO?
A DAO is an organization governed by smart contracts:
- No Central Authority: Decisions made by token holders
- Transparent: All proposals and votes on-chain
- Automated: Executes decisions automatically
- Democratic: One token = one vote (or weighted)
Core Components
1. Governance Token
RUSTstruct GovernanceToken { name: String, symbol: String, total_supply: u64, balances: HashMap<String, u64>, } impl GovernanceToken { fn voting_power(&self, holder: &str) -> u64 { self.balances.get(holder).copied().unwrap_or(0) } }
2. Proposals
RUSTenum ProposalStatus { Pending, Active, Succeeded, Defeated, Executed, Cancelled, } struct Proposal { id: u64, proposer: String, description: String, target: String, // Contract to call calldata: Vec<u8>, // Function call data start_block: u64, end_block: u64, status: ProposalStatus, for_votes: u64, against_votes: u64, abstain_votes: u64, }
3. Voting
RUSTenum VoteType { Against, For, Abstain, } struct Vote { proposal_id: u64, voter: String, support: VoteType, weight: u64, } struct DAO { proposals: HashMap<u64, Proposal>, votes: HashMap<(u64, String), Vote>, governance_token: GovernanceToken, quorum: u64, // Minimum votes needed voting_period: u64, // Blocks execution_delay: u64, // Blocks before execution }
Voting Mechanisms
Simple Majority
RUSTimpl DAO { fn vote(&mut self, proposal_id: u64, voter: &str, support: VoteType) -> Result<(), String> { let proposal = self.proposals.get_mut(&proposal_id) .ok_or("Proposal not found")?; if proposal.status != ProposalStatus::Active { return Err(String::from("Proposal not active")); } // Check if already voted if self.votes.contains_key(&(proposal_id, voter.to_string())) { return Err(String::from("Already voted")); } let voting_power = self.governance_token.voting_power(voter); match support { VoteType::For => proposal.for_votes += voting_power, VoteType::Against => proposal.against_votes += voting_power, VoteType::Abstain => proposal.abstain_votes += voting_power, } self.votes.insert( (proposal_id, voter.to_string()), Vote { proposal_id, voter: voter.to_string(), support, weight: voting_power, } ); Ok(()) } }
Quadratic Voting
RUST// Quadratic voting: voting power = sqrt(tokens) fn quadratic_voting_power(tokens: u64) -> u64 { (tokens as f64).sqrt() as u64 } impl DAO { fn vote_quadratic(&mut self, proposal_id: u64, voter: &str, support: VoteType) -> Result<(), String> { let tokens = self.governance_token.voting_power(voter); let voting_power = quadratic_voting_power(tokens); // Use quadratic voting power instead of linear // ... } }
Delegation
RUSTstruct Delegation { delegator: String, delegate: String, amount: u64, } impl DAO { fn delegate(&mut self, delegator: &str, delegate: &str, amount: u64) -> Result<(), String> { let balance = self.governance_token.voting_power(delegator); if balance < amount { return Err(String::from("Insufficient balance")); } // Transfer voting power // In real implementation: track delegations Ok(()) } fn get_voting_power(&self, voter: &str) -> u64 { // Own tokens + delegated tokens self.governance_token.voting_power(voter) } }
Proposal Lifecycle
RUSTimpl DAO { fn create_proposal( &mut self, proposer: &str, description: String, target: String, calldata: Vec<u8>, ) -> Result<u64, String> { // Check proposer has enough tokens let min_proposal_threshold = 1_000_000; // 1M tokens if self.governance_token.voting_power(proposer) < min_proposal_threshold { return Err(String::from("Insufficient voting power to propose")); } let proposal_id = self.proposals.len() as u64 + 1; let current_block = 1000; // Simplified let proposal = Proposal { id: proposal_id, proposer: proposer.to_string(), description, target, calldata, start_block: current_block, end_block: current_block + self.voting_period, status: ProposalStatus::Active, for_votes: 0, against_votes: 0, abstain_votes: 0, }; self.proposals.insert(proposal_id, proposal); Ok(proposal_id) } fn execute_proposal(&mut self, proposal_id: u64) -> Result<(), String> { let proposal = self.proposals.get_mut(&proposal_id) .ok_or("Proposal not found")?; // Check status if proposal.status != ProposalStatus::Succeeded { return Err(String::from("Proposal not succeeded")); } // Check quorum let total_votes = proposal.for_votes + proposal.against_votes + proposal.abstain_votes; if total_votes < self.quorum { return Err(String::from("Quorum not met")); } // Check majority if proposal.for_votes <= proposal.against_votes { return Err(String::from("Proposal defeated")); } // Execute proposal (call target contract) // In real implementation: call external contract proposal.status = ProposalStatus::Executed; Ok(()) } fn update_proposal_status(&mut self, proposal_id: u64, current_block: u64) { if let Some(proposal) = self.proposals.get_mut(&proposal_id) { if proposal.status == ProposalStatus::Active && current_block > proposal.end_block { let total_votes = proposal.for_votes + proposal.against_votes; if total_votes >= self.quorum && proposal.for_votes > proposal.against_votes { proposal.status = ProposalStatus::Succeeded; } else { proposal.status = ProposalStatus::Defeated; } } } } }
Treasury Management
RUSTstruct Treasury { balance: u64, token: String, authorized_spenders: HashSet<String>, } impl Treasury { fn new(token: String) -> Self { Treasury { balance: 0, token, authorized_spenders: HashSet::new(), } } fn deposit(&mut self, amount: u64) { self.balance += amount; } fn withdraw(&mut self, amount: u64, recipient: &str, proposal_id: u64) -> Result<(), String> { // Only authorized by proposal if !self.is_authorized_by_proposal(proposal_id) { return Err(String::from("Not authorized by proposal")); } if self.balance < amount { return Err(String::from("Insufficient treasury balance")); } self.balance -= amount; // Transfer to recipient Ok(()) } fn is_authorized_by_proposal(&self, proposal_id: u64) -> bool { // Check if proposal was executed and authorized this withdrawal // Simplified true } }
Real-World Examples
- Uniswap DAO: Governs Uniswap protocol
- MakerDAO: Governs DAI stablecoin
- Compound: Lending protocol governance
- Aragon: DAO framework
Best Practices
- Quorum: Require minimum participation
- Timelock: Delay execution for security
- Multisig: Require multiple signatures for critical operations
- Proposal Threshold: Minimum tokens to propose
- Voting Period: Sufficient time for discussion
Security Considerations
- Flash Loan Attacks: Prevent voting manipulation
- Proposal Spam: Require tokens to propose
- Timelock: Prevent immediate execution
- Multisig: Critical operations need multiple approvals
Code Examples
DAO Voting System
Basic DAO voting implementation
use std::collections::{HashMap, HashSet};
enum ProposalStatus {
Active,
Succeeded,
Defeated,
}
enum VoteType {
For,
Against,
}
struct Proposal {
id: u64,
description: String,
for_votes: u64,
against_votes: u64,
status: ProposalStatus,
end_block: u64,
}
struct Vote {
proposal_id: u64,
voter: String,
vote_type: VoteType,
weight: u64,
}
struct DAO {
proposals: HashMap<u64, Proposal>,
votes: HashMap<(u64, String), Vote>,
token_balances: HashMap<String, u64>,
quorum: u64,
}
impl DAO {
fn new(quorum: u64) -> Self {
DAO {
proposals: HashMap::new(),
votes: HashMap::new(),
token_balances: HashMap::new(),
quorum,
}
}
fn create_proposal(&mut self, id: u64, description: String, end_block: u64) {
self.proposals.insert(id, Proposal {
id,
description,
for_votes: 0,
against_votes: 0,
status: ProposalStatus::Active,
end_block,
});
}
fn vote(&mut self, proposal_id: u64, voter: &str, vote_type: VoteType) -> Result<(), String> {
let proposal = self.proposals.get_mut(&proposal_id)
.ok_or("Proposal not found")?;
if matches!(proposal.status, ProposalStatus::Succeeded | ProposalStatus::Defeated) {
return Err(String::from("Proposal not active"));
}
// Check if already voted
if self.votes.contains_key(&(proposal_id, voter.to_string())) {
return Err(String::from("Already voted"));
}
let voting_power = self.token_balances.get(voter).copied().unwrap_or(0);
match vote_type {
VoteType::For => proposal.for_votes += voting_power,
VoteType::Against => proposal.against_votes += voting_power,
}
self.votes.insert(
(proposal_id, voter.to_string()),
Vote {
proposal_id,
voter: voter.to_string(),
vote_type,
weight: voting_power,
}
);
Ok(())
}
fn finalize_proposal(&mut self, proposal_id: u64, current_block: u64) -> Result<(), String> {
let proposal = self.proposals.get_mut(&proposal_id)
.ok_or("Proposal not found")?;
if current_block < proposal.end_block {
return Err(String::from("Voting period not ended"));
}
let total_votes = proposal.for_votes + proposal.against_votes;
if total_votes < self.quorum {
proposal.status = ProposalStatus::Defeated;
return Ok(());
}
if proposal.for_votes > proposal.against_votes {
proposal.status = ProposalStatus::Succeeded;
} else {
proposal.status = ProposalStatus::Defeated;
}
Ok(())
}
}
fn main() {
let mut dao = DAO::new(1000); // Quorum: 1000 tokens
// Set token balances
dao.token_balances.insert(String::from("alice"), 500);
dao.token_balances.insert(String::from("bob"), 300);
dao.token_balances.insert(String::from("charlie"), 400);
// Create proposal
dao.create_proposal(1, String::from("Increase treasury funding"), 1000);
// Vote
dao.vote(1, "alice", VoteType::For).unwrap();
dao.vote(1, "bob", VoteType::For).unwrap();
dao.vote(1, "charlie", VoteType::Against).unwrap();
// Finalize
dao.finalize_proposal(1, 1001).unwrap();
let proposal = dao.proposals.get(&1).unwrap();
println!("Proposal status: {:?}", proposal.status);
println!("For: {}, Against: {}", proposal.for_votes, proposal.against_votes);
}Explanation:
DAOs enable decentralized governance. Token holders vote on proposals. Proposals need to meet quorum and have majority support to pass. Once passed, they can be executed to modify the protocol.
Treasury Management
DAO treasury with proposal-based withdrawals
use std::collections::HashMap;
struct Treasury {
balance: u64,
authorized_proposals: HashMap<u64, u64>, // proposal_id -> amount
}
impl Treasury {
fn new() -> Self {
Treasury {
balance: 0,
authorized_proposals: HashMap::new(),
}
}
fn deposit(&mut self, amount: u64) {
self.balance += amount;
}
fn authorize_withdrawal(&mut self, proposal_id: u64, amount: u64) -> Result<(), String> {
// Only called after proposal succeeds
self.authorized_proposals.insert(proposal_id, amount);
Ok(())
}
fn withdraw(&mut self, proposal_id: u64, recipient: &str) -> Result<u64, String> {
let amount = self.authorized_proposals.get(&proposal_id)
.copied()
.ok_or("Withdrawal not authorized")?;
if self.balance < amount {
return Err(String::from("Insufficient treasury balance"));
}
self.balance -= amount;
self.authorized_proposals.remove(&proposal_id);
println!("Withdrew {} to {}", amount, recipient);
Ok(amount)
}
fn get_balance(&self) -> u64 {
self.balance
}
}
fn main() {
let mut treasury = Treasury::new();
// Deposit funds
treasury.deposit(10_000_000);
println!("Treasury balance: {}", treasury.get_balance());
// Authorize withdrawal (after proposal passes)
treasury.authorize_withdrawal(1, 1_000_000).unwrap();
// Execute withdrawal
treasury.withdraw(1, "0xRecipient").unwrap();
println!("Remaining balance: {}", treasury.get_balance());
}Explanation:
DAO treasuries hold funds that can only be spent through governance proposals. Proposals must pass voting, then withdrawals are authorized. This ensures transparent and democratic fund management.
Exercises
Simple Voting
Create a simple voting system!
Starter Code:
struct Proposal {
for_votes: u64,
against_votes: u64,
}
fn main() {
// Create proposal
// Add votes
// Check result
}