DAO Governance: Voting, Proposals, and Treasury

Implementing Decentralized Autonomous Organizations with voting mechanisms, proposals, and treasury management.

Advanced⏱️ 60 min📚 Prerequisites: 2

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

RUST
struct 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

RUST
enum 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

RUST
enum 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

RUST
impl 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

RUST
struct 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

RUST
impl 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

RUST
struct 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

  1. Quorum: Require minimum participation
  2. Timelock: Delay execution for security
  3. Multisig: Require multiple signatures for critical operations
  4. Proposal Threshold: Minimum tokens to propose
  5. 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

RUST
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

RUST
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!

Easy

Starter Code:

RUST
struct Proposal {
    for_votes: u64,
    against_votes: u64,
}

fn main() {
    // Create proposal
    // Add votes
    // Check result
}