Skip to main content

Overview

A full on-chain governance system. Token holders can create proposals, vote, and execute transactions through a timelock-protected treasury. This follows the OpenZeppelin Governor pattern.
DifficultyAdvanced
CategoryDAO
ChainsEthereum
ServicesNone
LicenseApache-2.0

Quick Start

dokrypt init my-dao --template evm-dao
cd my-dao
dokrypt up

Generated Files

my-dao
dokrypt.yaml
foundry.toml
README.md
contracts
token
VotingToken.sol
governance
Governor.sol
Timelock.sol
Treasury.sol
test
token
VotingToken.t.sol
governance
Governor.t.sol
Timelock.t.sol
Treasury.t.sol
scripts
deploy-dao.js
create-proposal.js

Governance Lifecycle

  1. Propose — A token holder creates a proposal (requires >= 100 tokens)
  2. Voting Delay — 1 block before voting opens
  3. Voting Period — 45,818 blocks (~1 week at 12s/block) for token holders to vote
  4. Succeed/Defeat — Proposal passes if quorum (10,000 tokens) reached and more For than Against votes
  5. Queue — Passed proposal is queued in the Timelock
  6. Timelock — 1 day (86,400 seconds) mandatory waiting period
  7. Execute — After the delay, anyone can execute the proposal

Contracts

VotingToken.sol

ERC-20 with vote delegation and checkpoint-based historical voting power tracking. Constructor:
constructor(string memory name, string memory symbol, uint256 initialSupply)
Key Functions:
FunctionAccessDescription
delegate(delegatee)PublicDelegate your voting power
getVotes(account)ViewCurrent voting power
getPriorVotes(account, blockNumber)ViewHistorical voting power at a specific block
Standard ERC-20transfer, approve, transferFrom, balanceOf, allowance
Checkpoint System:
struct Checkpoint {
    uint256 fromBlock;  // Block when recorded
    uint256 votes;      // Voting power
}
Checkpoints are recorded whenever delegation changes, enabling accurate historical voting power lookups.
Token holders must call delegate() (even self-delegation) before their tokens count as voting power. Undelegated tokens have zero voting power.

Governor.sol

The core governance engine that manages proposals and voting. Configuration:
ParameterValueDescription
votingDelay1 blockDelay before voting starts
votingPeriod45,818 blocks~1 week voting window
quorum10,000 tokensMinimum votes for validity
proposalThreshold100 tokensMinimum tokens to propose
Proposal States: PendingActiveCanceled / Defeated / SucceededQueuedExecuted Functions:
FunctionAccessDescription
propose(targets[], values[], calldatas[], description)Token holderCreate a proposal
castVote(proposalId, support)Token holderVote: 0=Against, 1=For, 2=Abstain
castVoteWithReason(proposalId, support, reason)Token holderVote with explanation
queueProposal(proposalId)PublicQueue a succeeded proposal
executeProposal(proposalId)PublicExecute a queued proposal
cancel(proposalId)Proposer/AdminCancel a proposal
state(proposalId)ViewGet current proposal state
getProposalVotes(proposalId)ViewGet vote counts
Events: ProposalCreated, VoteCast, ProposalExecuted, ProposalCanceled, ProposalQueued

Timelock.sol

Enforces a mandatory delay before executing governance decisions. This gives token holders time to react to passed proposals. Configuration:
ParameterDefaultDescription
minDelay86,400 seconds (1 day)Minimum execution delay
Roles:
  • Proposer — Can schedule operations (Governor contract)
  • Executor — Can execute ready operations (Governor contract)
  • Admin — Can manage roles and update delay
Functions:
FunctionAccessDescription
schedule(target, value, data, salt, delay)ProposerSchedule an operation
execute(target, value, data, salt)ExecutorExecute a ready operation
cancel(id)ProposerCancel a scheduled operation
isOperationReady(id)ViewCheck if operation can be executed
addProposer(account)AdminGrant proposer role
addExecutor(account)AdminGrant executor role
updateMinDelay(newDelay)AdminChange minimum delay
Events: CallScheduled, CallExecuted, Cancelled, MinDelayChanged

Treasury.sol

Holds ETH and ERC-20 tokens for the DAO. Only the Timelock (via Governor proposals) can withdraw funds. Functions:
FunctionAccessDescription
deposit(token, amount)PublicDeposit ERC-20 tokens
receive() / fallback()PublicAccept ETH
withdraw(token, to, amount)Controller (Timelock)Withdraw ERC-20
withdrawETH(to, amount)Controller (Timelock)Withdraw ETH
getBalance(token)ViewCheck token balance
updateController(newController)AdminUpdate controller address
Events: Deposited, ETHDeposited, Withdrawn, ETHWithdrawn, ControllerUpdated

Deployment

npx hardhat run scripts/deploy-dao.js --network localhost
Deployment order:
  1. VotingToken — 1,000,000 initial supply
  2. Timelock — 1 day delay
  3. Governor — Connected to VotingToken and Timelock
  4. Treasury — Controlled by Timelock
The script also sets up roles and self-delegates the deployer’s voting power.

Testing with Time Travel

# Create a proposal, then advance past voting delay
dokrypt chain mine 2

# Cast votes, then advance past voting period
dokrypt chain mine 45820

# Queue the proposal, then advance past timelock
dokrypt chain time-travel 1d
dokrypt chain mine 1

# Execute the proposal