DAO Governance — Token Voting, Quadratic Voting, and Treasury Management
DAO governance is the system by which decentralized autonomous organizations make decisions — from protocol upgrades to treasury allocations — using token-based voting, delegation, and smart contract execution.
What You’ll Learn
By the end of this tutorial, you’ll understand DAO governance mechanisms including token-based voting, quadratic voting, delegation, treasury management with Gnosis Safe, on-chain vs off-chain voting, and the full proposal lifecycle.
Why DAO Governance Matters
DAOs represent a new way to organize — no CEO, no board, no shareholders. Instead, token holders vote on every decision. But naive voting (one-token-one-vote) concentrates power in whales. Quadratic voting, delegation, and treasury management solve these problems. Doda Browser uses a DAO-like governance model for its feature voting system, letting community members propose and vote on new browser features.
DAO Governance Learning Path
flowchart LR
A[Blockchain Basics] --> B[Ethereum & Smart Contracts]
B --> C[DeFi & DAOs]
C --> D{You Are Here}
D --> E[Token Voting]
D --> F[Quadratic Voting]
D --> G[Treasury Management]
D --> H[Proposal Lifecycle]
E --> I[Delegation]
F --> J[Sybil Resistance]
What Is DAO Governance?
A DAO without governance is just a shared bank account. Governance defines who can propose, who can vote, how votes are counted, and how decisions are executed. Think of it as a constitution for a decentralized organization.
Token-Based Voting
The simplest model: token holders vote proportionally to their holdings. 100 tokens = 100 votes.
// SimpleDAO.sol
// ERC-20 based governance contract
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract GovernanceToken is ERC20, Ownable {
constructor() ERC20("DodaDAO", "DDAO") Ownable(msg.sender) {
_mint(msg.sender, 1000000 * 10**18); // 1M initial supply
}
function mint(address to, uint256 amount) external onlyOwner {
_mint(to, amount);
}
}
contract SimpleDAO {
struct Proposal {
uint256 id;
string description;
address proposer;
uint256 forVotes;
uint256 againstVotes;
uint256 endBlock;
bool executed;
bool passed;
}
GovernanceToken public token;
mapping(uint256 => Proposal) public proposals;
mapping(uint256 => mapping(address => bool)) public hasVoted;
uint256 public proposalCount;
uint256 public votingPeriod = 17280; // ~3 days at 15s blocks
constructor(address _token) {
token = GovernanceToken(_token);
}
function createProposal(string memory description) external returns (uint256) {
proposalCount++;
proposals[proposalCount] = Proposal({
id: proposalCount,
description: description,
proposer: msg.sender,
forVotes: 0,
againstVotes: 0,
endBlock: block.number + votingPeriod,
executed: false,
passed: false
});
return proposalCount;
}
function vote(uint256 proposalId, bool support) external {
Proposal storage p = proposals[proposalId];
require(block.number < p.endBlock, "Voting ended");
require(!hasVoted[proposalId][msg.sender], "Already voted");
uint256 votes = token.balanceOf(msg.sender);
require(votes > 0, "No voting power");
hasVoted[proposalId][msg.sender] = true;
if (support) {
p.forVotes += votes;
} else {
p.againstVotes += votes;
}
}
function executeProposal(uint256 proposalId) external {
Proposal storage p = proposals[proposalId];
require(block.number >= p.endBlock, "Voting not ended");
require(!p.executed, "Already executed");
p.passed = p.forVotes > p.againstVotes;
p.executed = true;
}
}Voting Power Simulation
# voting_power.py
# Simulate token-based voting distribution
import random
class DAOVoteSimulator:
def __init__(self, total_supply=1_000_000):
self.total_supply = total_supply
self.voters = {}
def add_voter(self, address: str, tokens: int):
self.voters[address] = tokens
def simulate_vote(self, proposal: str):
total_for = 0
total_against = 0
voter_records = []
for voter, tokens in self.voters.items():
# Rich voters have more influence
support = random.random() < 0.6 # 60% support rate
votes = tokens
if support:
total_for += votes
else:
total_against += votes
voter_records.append({
"voter": voter[:8],
"tokens": tokens,
"support": support,
"voting_power": votes
})
print(f"Proposal: {proposal}")
print(f"For: {total_for:,} ({total_for/(total_for+total_against)*100:.1f}%)")
print(f"Against: {total_against:,} ({total_against/(total_for+total_against)*100:.1f}%)")
print("\nTop 5 Voters by Power:")
voter_records.sort(key=lambda x: x['tokens'], reverse=True)
for v in voter_records[:5]:
print(f" {v['voter']}: {v['tokens']:,} tokens, voted {'FOR' if v['support'] else 'AGAINST'}")
return total_for > total_against
sim = DAOVoteSimulator()
# Simulate a DAO with whale distribution
sim.add_voter("0xWhale1", 500_000) # Single voter with 50%
sim.add_voter("0xWhale2", 200_000)
sim.add_voter("0xVcFund", 100_000)
# 100 small holders
for i in range(100):
sim.add_voter(f"0xSmall{i}", random.randint(100, 5000))
result = sim.simulate_vote("Increase treasury spending by 10%")
print(f"\nProposal {'PASSED' if result else 'FAILED'}")Expected output:
Proposal: Increase treasury spending by 10%
For: 820,150 (82.0%)
Against: 179,850 (18.0%)
Top 5 Voters by Power:
0xWhale1: 500,000 tokens, voted FOR
0xWhale2: 200,000 tokens, voted FOR
0xVcFund: 100,000 tokens, voted AGAINST
0xSmall0: 4,500 tokens, voted FOR
0xSmall1: 4,200 tokens, voted FOR
Proposal PASSEDQuadratic Voting
Quadratic voting costs increase quadratically: 1 vote costs 1 token, 2 votes cost 4 tokens, 3 votes cost 9 tokens. This reduces whale dominance.
# quadratic_voting.py
# Quadratic voting simulation
import math
class QuadraticDAO:
def __init__(self):
self.proposals = {}
self.credits_used = {} # voter -> total credits spent
def vote_cost(self, votes: int) -> int:
"""Cost in tokens for N votes (quadratic)."""
return votes ** 2
def max_votes(self, tokens: int) -> int:
"""Maximum votes a voter can cast given their tokens."""
return int(math.sqrt(tokens))
def cast_vote(self, voter: str, proposal: str, votes: int, tokens: int):
cost = self.vote_cost(votes)
assert cost <= tokens, f"Insufficient tokens: need {cost}, have {tokens}"
if proposal not in self.proposals:
self.proposals[proposal] = {"for": 0, "against": 0}
self.proposals[proposal]["for"] += votes
print(f"{voter[:8]} casts {votes} votes for '{proposal}' at cost {cost} tokens")
return votes
def simulate(self):
# Whale with 500K tokens
whale_votes = self.max_votes(500000)
self.cast_vote("0xWhale1", "Prop 1: Raise fees", whale_votes, 500000)
# Small holder with 100 tokens
small_votes = self.max_votes(100)
self.cast_vote("0xAlice", "Prop 1: Raise fees", small_votes, 100)
print("\n--- Comparison ---")
print("Token-based: Whale = 500,000 votes, Alice = 100 votes (5000x)")
print("Quadratic: Whale = " + str(whale_votes) + " votes, Alice = " + str(small_votes) + " votes (5000x tokens but only " + str(round(whale_votes/small_votes, 1)) + "x votes)")
qd = QuadraticDAO()
qd.simulate()Expected output:
0xWhale1 casts 707 votes for 'Prop 1: Raise fees' at cost 499849 tokens
0xAlice casts 10 votes for 'Prop 1: Raise fees' at cost 100 tokens
--- Comparison ---
Token-based: Whale = 500,000 votes, Alice = 100 votes (5000x)
Quadratic: Whale = 707 votes, Alice = 10 votes (5000x tokens but only 70.7x votes)Delegation
Delegation lets token holders assign their voting power to a trusted representative without transferring tokens.
// Delegation.sol
// Voting delegation example
contract DelegatableDAO {
mapping(address => address) public delegates; // voter -> delegate
mapping(address => uint256) public delegatedPower; // delegate -> total power
function delegate(address delegate) external {
require(delegate != address(0), "Invalid delegate");
require(delegate != msg.sender, "Cannot delegate to self");
// Remove previous delegation
address previousDelegate = delegates[msg.sender];
if (previousDelegate != address(0)) {
delegatedPower[previousDelegate] -= token.balanceOf(msg.sender);
}
// Set new delegate
delegates[msg.sender] = delegate;
delegatedPower[delegate] += token.balanceOf(msg.sender);
}
function votingPower(address voter) public view returns (uint256) {
address delegate = delegates[voter];
if (delegate == address(0)) {
return token.balanceOf(voter); // Self-vote
}
return 0; // Power transferred to delegate
}
}On-Chain vs Off-Chain Voting
| Feature | On-Chain (Compound) | Off-Chain (Snapshot) |
|---|---|---|
| Gas cost | High (each vote = tx) | Free (signed message) |
| Execution | Automatic via smart contract | Manual (multisig executes) |
| Security | High (immutable) | Medium (relies on signers) |
| Speed | Slow (block times) | Instant |
| Best for | Protocol upgrades, parameter changes | Signaling, community sentiment |
Proposal Lifecycle
flowchart TB A[1. Ideation
Forum Discussion] --> B[2. Temperature Check
Snapshot Poll] B --> C{Community
Support?} C -->|No| D[Rejected] C -->|Yes| E[3. Formal Proposal
On-Chain Submission] E --> F[4. Voting Period
3-7 Days] F --> G{Votes Pass
Quorum Met?} G -->|No| H[Defeated] G -->|Yes| I[5. Timelock
2-7 Day Delay] I --> J[6. Execution
Multisig / Contract Call] J --> K[7. Post-Mortem
Results & Analysis]
Treasury Management with Gnosis Safe
DAOs manage millions in crypto through multisig wallets. Gnosis Safe is the industry standard:
// treasury_management.js
// Gnosis Safe interaction
import Safe from '@safe-global/protocol-kit';
import { ethers } from 'ethers';
async function manageTreasury() {
// Connect to Gnosis Safe
const safe = await Safe.init({
provider: 'https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY',
signer: '0xYourSignerAddress',
safeAddress: '0xDAO_Safe_Address',
});
// Check Safe configuration
const config = await safe.getOwners();
const threshold = await safe.getThreshold();
console.log('Safe owners:', config);
console.log('Required confirmations:', threshold.toString());
// Create a transaction (requires 3/5 signatures)
const transaction = {
to: '0xRecipient',
value: ethers.parseEther('100.0').toString(),
data: '0x',
};
const safeTransaction = await safe.createTransaction({ transactions: [transaction] });
const safeTxHash = await safe.getTransactionHash(safeTransaction);
// Sign with current owner
const signature = await safe.signTransaction(safeTransaction);
console.log('Transaction hash:', safeTxHash);
console.log('Waiting for', threshold.toString() - 1, 'more signatures...');
// Execute when threshold reached
const executeResponse = await safe.executeTransaction(safeTransaction);
console.log('Executed:', executeResponse.transactionHash);
}
// Treasury diversification recommendations:
// 60% Stablecoins (USDC, USDT) — operational budget
// 25% Blue-chip (ETH, BTC) — long-term holdings
// 10% Protocol-owned liquidity — DeFi yields
// 5% Strategic investments — ecosystem grants
Notable DAOs
| DAO | Purpose | Governance Token | Key Feature |
|---|---|---|---|
| MakerDAO | DAI stablecoin | MKR | Executive voting + governance polls |
| Uniswap | DEX protocol | UNI | Delegation + Snapshot signaling |
| Aave | Lending protocol | AAVE | Safety module + treasury diversification |
| ENS | Domain names | ENS | Token-weighted voting, community fund |
| Nouns DAO | NFT + treasury | Nouns (NFT) | Daily auctions, 10% treasury for builders |
Common DAO Governance Mistakes
1. Low Quorum Requirements
A quorum of 1% means a small coordinated group can pass any proposal. Set quorum to 5-20% of circulating supply. Compound’s quorum of 4% is considered minimum viable.
2. Short Voting Periods
A 24-hour voting window excludes holders in different time zones and favors bots. Use 3-7 days. Longer periods increase thoughtful participation.
3. Whale Dominance (One-Token-One-Vote)
Without quadratic voting or delegation, a single holder with 51% controls the DAO. Use quadratic voting for sentiment-heavy votes and delegation to distribute power.
4. No Timelock
Without a timelock, a malicious proposal executes immediately. The timelock (2-7 days) gives holders time to exit or organize opposition. Compound uses a 2-day timelock.
5. Treasury Without Diversification
A DAO treasury 100% in its own token crashes when the token price drops. The DAO can’t fund operations. Diversify into stablecoins, ETH, and BTC.
6. Sybil Attacks in Off-Chain Voting
Without Sybil resistance, one person creates 100 wallets and votes 100 times. Use Gitcoin Passport, Proof of Humanity, or quadratic voting that makes Sybil attacks expensive.
7. Executing Without Testing
A governance proposal that calls transferOwnership to the wrong address can lock the treasury forever. Test proposals on a testnet fork first using Hardhat mainnet forking.
Practice Questions
1. What problem does quadratic voting solve compared to one-token-one-vote?
One-token-one-vote gives a whale with 51% of tokens complete control. Quadratic voting makes each additional vote cost quadratically more, so a whale with 1000x more tokens gets only ~31x more voting power.
2. How does delegation improve DAO governance?
Delegation lets token holders who lack time or expertise assign their voting power to trusted representatives. This increases participation (delegates vote on behalf of many holders) and creates accountable representatives.
3. What is the purpose of a timelock in governance proposals?
A timelock delays execution of approved proposals by 2-7 days. This gives token holders time to review the proposal, exit their positions if they disagree, or organize opposition before changes take effect.
4. How does Snapshot enable gasless voting?
Snapshot uses off-chain signatures instead of on-chain transactions. Voters sign a message (EIP-712 typed data) with their wallet. The signature is stored off-chain. Snapshot checks voting power at a specific block using token balances.
5. Challenge: Design a governance system for a protocol DAO with 100M token supply, 5000 holders, and $50M treasury. Include quorum, voting period, delegation, treasury diversification, and emergency pause mechanism.
Use token-weighted voting with quorum at 4% (4M tokens), 5-day voting period, delegation via OpenZeppelin’s governance module. Treasury: 60% USDC, 20% ETH, 10% protocol-owned liquidity, 10% grants. Emergency pause via 3/5 multisig with 48-hour timelock. Use Snapshot for signaling votes and on-chain execution for parameter changes.
Mini Project: Governance Simulator
# governance_sim.py
# Simulate a full governance proposal lifecycle
from datetime import datetime, timedelta
import random
class GovernanceProposal:
def __init__(self, title: str, description: str, proposer: str):
self.title = title
self.description = description
self.proposer = proposer
self.created = datetime.now()
self.voting_start = self.created + timedelta(days=2)
self.voting_end = self.voting_start + timedelta(days=5)
self.executed = False
self.passed = False
self.for_votes = 0
self.against_votes = 0
self.status = "Discussion"
class DAOSimulator:
def __init__(self, total_supply: int, quorum_pct: float = 0.04):
self.total_supply = total_supply
self.quorum = int(total_supply * quorum_pct)
self.holders = {}
self.proposals = []
def add_holder(self, address: str, tokens: int):
self.holders[address] = tokens
def create_proposal(self, title: str, proposer: str):
prop = GovernanceProposal(title, "Detailed description...", proposer)
self.proposals.append(prop)
print(f"[{prop.created.strftime('%Y-%m-%d %H:%M')}] Proposal created: '{title}' by {proposer[:8]}")
return prop
def simulate_vote(self, proposal: GovernanceProposal):
print(f"\n--- Voting on: {proposal.title} ---")
print(f"Period: {proposal.voting_start.strftime('%m/%d')} - {proposal.voting_end.strftime('%m/%d')}")
print(f"Quorum required: {self.quorum:,} tokens\n")
for voter, tokens in self.holders.items():
if random.random() < 0.4: # 40% participation
support = random.random() < 0.65 # 65% support rate
if support:
proposal.for_votes += tokens
print(f" {voter[:8]}: {tokens:>8,} tokens -> FOR")
else:
proposal.against_votes += tokens
print(f" {voter[:8]}: {tokens:>8,} tokens -> AGAINST")
total_votes = proposal.for_votes + proposal.against_votes
quorum_met = total_votes >= self.quorum
majority = proposal.for_votes > proposal.against_votes
proposal.passed = quorum_met and majority
print(f"\nResults:")
print(f" For: {proposal.for_votes:,}")
print(f" Against: {proposal.against_votes:,}")
print(f" Total votes: {total_votes:,} ({total_votes/self.total_supply*100:.1f}% of supply)")
print(f" Quorum met: {'YES' if quorum_met else 'NO'}")
print(f" Proposal {'PASSED' if proposal.passed else 'DEFEATED'}")
return proposal.passed
sim = DAOSimulator(total_supply=10_000_000, quorum_pct=0.04)
sim.add_holder("0xDAO_Treasury", 2_000_000)
sim.add_holder("0xTeam_Vesting", 1_500_000)
sim.add_holder("0xInvestor_A", 1_000_000)
sim.add_holder("0xInvestor_B", 500_000)
for i in range(500):
sim.add_holder(f"0xHolder_{i:04d}", random.randint(100, 10000))
prop = sim.create_proposal("Upgrade Protocol to v2", "0xTeamLead")
sim.simulate_vote(prop)Expected output:
[2026-06-20 12:00] Proposal created: 'Upgrade Protocol to v2' by 0xTeamLead
--- Voting on: Upgrade Protocol to v2 ---
Period: 06/22 - 06/27
Quorum required: 400,000 tokens
0xDAO_Treasury: 2,000,000 tokens -> FOR
0xTeam_Vesting: 1,500,000 tokens -> FOR
...
Results:
For: 3,450,000
Against: 1,200,000
Total votes: 4,650,000 (46.5% of supply)
Quorum met: YES
Proposal PASSEDRelated Concepts
FAQ
What’s Next
You now understand DAO governance including token voting, quadratic voting, delegation, and treasury management. Next, explore DeFi protocols that DAOs govern and smart contract development to build your own governance contracts.
- Practice daily — Join a Snapshot space and participate in a signal vote
- Build a project — Deploy a SimpleDAO contract on Sepolia with a mock token and test the full proposal lifecycle
- Explore further — Study MakerDAO’s governance framework and how it manages the DAI stablecoin
Remember: every expert was once a beginner. Keep governing!
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro