Hardware FixRecommendedDevice not working? Your driver may be the problemCheck updates for common hardware issues.Fix DriversOctober DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsWindows FixRecommendedWindows errors stealing your time? Find the fix fastScan stability, cleanup and performance issues.Fix Now×
Skip to content
The Finance Base
The Money Desk · Blog
Re:

How to Build a DAO from Scratch with Solidity and Foundry, Part 1: Designing the Governance Token

A version-aware, local-first tutorial for designing, testing, and deploying the governance-token layer of a DAO with Solidity, OpenZeppelin, Foundry, and Anvil.
From TheFinanceBase Team7 min to read
Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

This first installment builds and tests only the governance-token layer of a DAO. You will compose OpenZeppelin’s ERC20Votes and ERC20Permit with Foundry, deploy the token to Anvil, and verify delegation and historical voting power. A token contract is not a complete DAO: a production system normally adds a Governor and Timelock.

The examples below target Solidity ^0.8.24 and OpenZeppelin Contracts 5-style APIs. Check the exact library tag and compiler version you install; older 4.x and newer prerelease versions may require different overrides. OpenZeppelin’s governance overview is at docs.openzeppelin.com/contracts/4.x/governance.

What a governance token must decide

An ERC-20 balance alone is not a reliable governance system. Before writing Solidity, decide who receives the initial supply, whether supply is fixed or mintable, how holders delegate, which historical timepoint a Governor will read, and whether voting uses blocks or timestamps. Also decide whether transfers remain possible during a vote, whether one token equals one vote, and what administrative powers survive deployment.

A common architecture is:

GovernanceToken → Governor → Timelock → treasury or governed contracts

The token records voting power. The Governor creates proposals and counts votes. A Timelock delays successful execution so participants can review or react. This article implements only the first component.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Why ERC20Votes matters

ERC20Votes stores checkpoints of delegated voting power. A Governor can therefore ask what an account controlled at a past timepoint instead of trusting its balance at the instant a vote is cast. That reduces flash-balance and transfer-timing manipulation.

  • getVotes(account) returns current effective voting power.
  • getPastVotes(account, timepoint) returns the checkpointed value for an earlier valid timepoint.
  • delegates(account) shows where an account’s voting power is delegated.
  • Transfers, minting, and burning update checkpoints for affected delegates.

A holder can have tokens but zero effective votes until delegating, commonly to themselves. Delegation is not automatic because it costs gas and checkpoint storage. Your eventual Governor must use the same clock mode as the token. Block-based and timestamp-based systems are not interchangeable, and the exact snapshot rule—proposal creation, activation, or another configured point—comes from the selected Governor configuration, not from ERC-20 itself. See OpenZeppelin’s governance documentation for compatibility details: https://docs.openzeppelin.com/contracts/4.x/governance.

What ERC20Permit does—and does not do

ERC20Permit implements EIP-2612 signature-based approvals. A user signs EIP-712 typed data containing the token domain, spender, amount, nonce, and deadline; a relayer submits the transaction. Nonces prevent replay, the chain ID participates in the domain separator, and an expired deadline invalidates the signature.

Permit is not automatically gasless governance. permit approves token spending; delegate is a separate state-changing call, and delegateBySig is a separate governance-signature flow when supported. Wallet support and frontend implementation also vary. Test invalid signers, expired deadlines, reused nonces, wrong-chain domains, and mismatched spender or recipient data.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Supply and authority are governance decisions

Design Advantage Risk or cost
Fixed supply Predictable dilution and voting power No future issuance without another mechanism
Capped minting Allows planned grants within a hard limit The cap and authorization must be governed
Owner minting Simple for a tutorial One key can dilute holders and seize governance
Governor-controlled minting Issuance follows on-chain governance Requires a secure Governor and Timelock
Vesting allocations Reduces immediate insider liquidity Additional contracts and edge cases
Treasury allocation Funds future grants and operations A concentrated treasury can dominate votes

The baseline below uses a fixed one-million-token supply and omits an owner mint function. An unrestricted mint is acceptable only as an explicitly local-development shortcut, or when it is capped, timelocked, and controlled by carefully reviewed governance. A deployer receiving the entire initial supply is also centralized until distribution, vesting, treasury, or delegation policies change that outcome.

Install and pin the toolchain

Foundry supplies Forge (builds and tests), Cast (RPC calls), Anvil (a local EVM node), and Chisel. Install it with the official command:

curl -L https://foundry.paradigm.xyz | bash
foundryup
forge --version
cast --version
anvil --version

Foundry changes quickly; record the versions printed by these commands rather than treating any release as permanently current. The official repository is https://github.com/foundry-rs/foundry.

Create a project:

mkdir DAO
cd DAO
forge init

Foundry creates foundry.toml, lib/, script/, src/, and test/. Remove the generated Counter example. Install OpenZeppelin, then pin a release tag or commit rather than following a moving branch:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
forge install OpenZeppelin/openzeppelin-contracts

Confirm remappings.txt contains:

@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/

OpenZeppelin’s repository explains its audited release channels and dependency guidance: https://github.com/OpenZeppelin/openzeppelin-contracts.

Implement the fixed-supply token

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {ERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
import {ERC20Votes} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol";
import {Nonces} from "@openzeppelin/contracts/utils/Nonces.sol";

contract GovernanceToken is ERC20, ERC20Permit, ERC20Votes {
    constructor()
        ERC20("GovernanceToken", "MGT")
        ERC20Permit("GovernanceToken")
    {
        _mint(msg.sender, 1_000_000 * 10 ** decimals());
    }

    function _update(address from, address to, uint256 amount)
        internal
        override(ERC20, ERC20Votes)
    {
        super._update(from, to, amount);
    }

    function nonces(address owner)
        public
        view
        override(ERC20Permit, Nonces)
        returns (uint256)
    {
        return super.nonces(owner);
    }
}

The constructor mints 1,000,000 whole tokens. With the default 18 decimals, totalSupply() is 1_000_000 × 1018 = 1e24 raw units. The ether unit in Solidity tests is merely a convenient 10**18 multiplier; it does not mean the token is ETH.

Why the overrides are required

ERC20 and ERC20Votes both participate in balance updates, while ERC20Permit and Nonces expose overlapping nonce behavior. The overrides resolve Solidity’s multiple-inheritance graph and call parent logic through super; they are not custom tokenomics. Exact requirements depend on the OpenZeppelin major version, so copying 4.x code into a 5.x project can fail to compile.

Compile and diagnose errors

forge build

A successful build writes artifacts under out/. If it fails, inspect the first error:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Rank #4
Sale
Mastering Bitcoin: Programming the Open Blockchain
  • Brand New in box. The product ships with all relevant accessories
  1. Compare the contract pragma with the installed OpenZeppelin release.
  2. Check the remapping and that the dependency is initialized.
  3. Confirm whether the code expects OpenZeppelin 4.x or 5.x inheritance APIs.
  4. Run forge clean, then rebuild.
  5. Pin dependencies before updating them; do not blindly pull a development branch.

Test voting behavior, not just minting

A single balance test does not establish governance correctness. At minimum, cover the following:

function testInitialSupply() public {
    assertEq(token.totalSupply(), 1_000_000 ether);
    assertEq(token.balanceOf(address(this)), 1_000_000 ether);
}

function testSelfDelegation() public {
    token.delegate(address(this));
    assertEq(token.getVotes(address(this)), 1_000_000 ether);
}

Also test that account A delegates to A, transfers tokens to B, and loses current votes by the transferred amount; B has a balance but no active votes until delegating; and getPastVotes still returns the earlier checkpoint. Use mined blocks or controlled timepoints appropriate to the token clock.

  • Name, symbol, decimals, initial allocation, and transfer behavior.
  • Delegation to another account and checkpoint changes after transfers.
  • Permit success, nonce increment, wrong signer, expired deadline, reused nonce, and wrong-chain domain.
  • Zero-address and zero-amount edge cases.
  • Fuzzed transfers and delegation invariants, including total-supply conservation.

Run the suite with:

forge test
forge test -vvv
forge test --match-test testSelfDelegation
forge test --gas-report
forge coverage
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Deploy locally with Anvil

Start the local node:

anvil

Anvil normally listens at http://127.0.0.1:8545, uses chain ID 31337, and prints prefunded accounts and private keys. Those keys are for local testing only. The local-EVM workflow is described at https://docs.openzeppelin.com/monitor/local-evm-testing.

Create script/DeployGovernanceToken.s.sol:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {Script} from "forge-std/Script.sol";
import {GovernanceToken} from "../src/GovernanceToken.sol";

contract DeployGovernanceToken is Script {
    function run() external returns (GovernanceToken token) {
        vm.startBroadcast();
        token = new GovernanceToken();
        vm.stopBroadcast();
    }
}

Broadcast it with an Anvil key:

forge script script/DeployGovernanceToken.s.sol 
  --rpc-url http://127.0.0.1:8545 
  --broadcast 
  --private-key <ANVIL_PRIVATE_KEY>

Query the resulting address:

cast call <DEPLOYED_CONTRACT_ADDRESS> 
  "totalSupply()(uint256)" 
  --rpc-url http://127.0.0.1:8545

cast call <DEPLOYED_CONTRACT_ADDRESS> 
  "balanceOf(address)(uint256)" <DEPLOYER_ADDRESS> 
  --rpc-url http://127.0.0.1:8545

cast call <DEPLOYED_CONTRACT_ADDRESS> 
  "delegates(address)(address)" <DEPLOYER_ADDRESS> 
  --rpc-url http://127.0.0.1:8545

cast call <DEPLOYED_CONTRACT_ADDRESS> 
  "getVotes(address)(uint256)" <DEPLOYER_ADDRESS> 
  --rpc-url http://127.0.0.1:8545

The expected raw supply is 1000000000000000000000000. Before self-delegation, the deployer’s balance can be that amount while getVotes is zero.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Ownership and mainnet boundaries

Ownable(msg.sender) or an equivalent deployer authority is convenient locally, but a single externally owned account should not remain a permanent production minter or administrator. If privileged functions are necessary, transfer control to a multisig, a TimelockController, or the Governor’s execution path only after verifying addresses and recovery procedures. Transferring or renouncing ownership incorrectly can permanently remove control.

Before public deployment, pin Solidity, Foundry, OpenZeppelin, and forge-std; review allocations and dilution; test Governor/token clock compatibility; secure signing keys; deploy to a testnet; verify source through Etherscan or Sourcify; and obtain an independent security review where real funds are involved. OpenZeppelin’s preparation guidance is at https://docs.openzeppelin.com/contracts/5.x/learn/preparing-for-mainnet. Verification is useful, but neither OpenZeppelin components nor an audit guarantees that your integration is bug-free.

What this part deliberately leaves for later

  • Governor quorum, proposal threshold, voting delay, and voting period.
  • Timelock configuration and proposal execution.
  • Treasury custody, emergency procedures, and upgrade policy.
  • Delegation interfaces, indexing, and frontend wallet support.
  • Public-network RPC operations, monitoring, and deployment verification.

You now have a reproducible local token layer with checkpointed voting power and signature-based approvals. Whether it becomes a credible DAO depends on distribution, authority, Governor configuration, Timelock security, and operational controls—not merely on a successful compilation.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

More post from the Money Desk

  1. The Money DeskBlogTheFinanceBase07 MAR 2625 minWhat Is a 457 Plan?
  2. The Money DeskBlogTheFinanceBase07 MAR 2621 minTime Value of Money: What It Is and How It Works
  3. The Money DeskBlogTheFinanceBase07 MAR 2627 minAre You Living in One of These Top 10 Most Expensive Cities to Retire?
Recommended PC Tool
Recommended PC Tool
Windows Errors? Fix Them Before They SpreadFree repair scan
Crashes, No Sound, or Screen Glitches?Free driver scan

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.