HiHuo
首页
博客
手册
工具
关于
首页
博客
手册
工具
关于
  • Web3 完整技术体系

    • Web3 完整技术体系
    • 区块链基础与密码学
    • 第一章:比特币原理与实现
    • 第二章:以太坊架构与核心概念
    • Solidity智能合约开发基础
    • 04-Solidity进阶与安全
    • 05-ERC标准详解
    • 06-DeFi核心协议-去中心化交易所
    • 07-DeFi核心协议-借贷协议
    • 08-DeFi核心协议-稳定币
    • NFT与元宇宙
    • Layer2扩容方案
    • 跨链技术
    • Web3前端开发
    • 链下数据与预言机
    • 智能合约测试与部署
    • MEV与交易优化
    • DAO治理
    • 项目实战:完整DeFi借贷协议

DAO治理

1. DAO基础概念

1.1 什么是DAO

DAO (Decentralized Autonomous Organization,去中心化自治组织) 是一种基于区块链的组织形式,通过智能合约实现治理规则,由代币持有者共同决策。

核心特征:

  • 去中心化决策:没有单一的控制者
  • 透明治理:所有提案和投票公开可见
  • 自动执行:通过智能合约自动执行决议
  • 代币化权益:使用治理代币表示投票权

1.2 DAO的类型

DAO分类
├── 协议DAO
│   ├── DeFi协议治理 (Uniswap, Compound)
│   └── 基础设施治理 (ENS)
├── 投资DAO
│   ├── 风险投资 (The LAO)
│   └── 收藏品投资 (FlamingoDAO)
├── 社区DAO
│   ├── 社交组织
│   └── 创作者DAO
└── 服务DAO
    ├── 开发者组织
    └── 咨询服务

1.3 治理流程

典型治理流程:
1. 提案创建 → 2. 社区讨论 → 3. 投票表决 → 4. 时间锁 → 5. 执行

2. 治理代币

2.1 ERC20投票代币

// contracts/GovernanceToken.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract GovernanceToken is ERC20, ERC20Votes, ERC20Permit, Ownable {
    uint256 public constant MAX_SUPPLY = 100_000_000 * 10**18; // 1亿代币

    constructor()
        ERC20("Governance Token", "GOV")
        ERC20Permit("Governance Token")
        Ownable(msg.sender)
    {
        _mint(msg.sender, 10_000_000 * 10**18); // 初始供应1000万
    }

    function mint(address to, uint256 amount) external onlyOwner {
        require(totalSupply() + amount <= MAX_SUPPLY, "Exceeds max supply");
        _mint(to, amount);
    }

    // 以下函数是必需的覆盖

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

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

2.2 投票权委托

// 用户需要委托投票权才能参与治理
// 可以委托给自己或其他地址

// 委托给自己
token.delegate(msg.sender);

// 委托给其他人
token.delegate(delegateAddress);

// 批量委托(使用EIP-712签名)
token.delegateBySig(
    delegatee,
    nonce,
    expiry,
    v,
    r,
    s
);

3. Governor合约

3.1 完整的Governor实现

// contracts/MyGovernor.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/governance/Governor.sol";
import "@openzeppelin/contracts/governance/extensions/GovernorSettings.sol";
import "@openzeppelin/contracts/governance/extensions/GovernorCountingSimple.sol";
import "@openzeppelin/contracts/governance/extensions/GovernorVotes.sol";
import "@openzeppelin/contracts/governance/extensions/GovernorVotesQuorumFraction.sol";
import "@openzeppelin/contracts/governance/extensions/GovernorTimelockControl.sol";

contract MyGovernor is
    Governor,
    GovernorSettings,
    GovernorCountingSimple,
    GovernorVotes,
    GovernorVotesQuorumFraction,
    GovernorTimelockControl
{
    constructor(
        IVotes _token,
        TimelockController _timelock
    )
        Governor("MyGovernor")
        GovernorSettings(
            7200,  // 投票延迟: 1天 (assuming 12s blocks)
            50400, // 投票期间: 1周
            100e18 // 提案门槛: 100 tokens
        )
        GovernorVotes(_token)
        GovernorVotesQuorumFraction(4) // 4% quorum
        GovernorTimelockControl(_timelock)
    {}

    // 必需的覆盖函数

    function votingDelay()
        public
        view
        override(Governor, GovernorSettings)
        returns (uint256)
    {
        return super.votingDelay();
    }

    function votingPeriod()
        public
        view
        override(Governor, GovernorSettings)
        returns (uint256)
    {
        return super.votingPeriod();
    }

    function quorum(uint256 blockNumber)
        public
        view
        override(Governor, GovernorVotesQuorumFraction)
        returns (uint256)
    {
        return super.quorum(blockNumber);
    }

    function state(uint256 proposalId)
        public
        view
        override(Governor, GovernorTimelockControl)
        returns (ProposalState)
    {
        return super.state(proposalId);
    }

    function proposalNeedsQueuing(uint256 proposalId)
        public
        view
        override(Governor, GovernorTimelockControl)
        returns (bool)
    {
        return super.proposalNeedsQueuing(proposalId);
    }

    function proposalThreshold()
        public
        view
        override(Governor, GovernorSettings)
        returns (uint256)
    {
        return super.proposalThreshold();
    }

    function _queueOperations(
        uint256 proposalId,
        address[] memory targets,
        uint256[] memory values,
        bytes[] memory calldatas,
        bytes32 descriptionHash
    ) internal override(Governor, GovernorTimelockControl) returns (uint48) {
        return super._queueOperations(proposalId, targets, values, calldatas, descriptionHash);
    }

    function _executeOperations(
        uint256 proposalId,
        address[] memory targets,
        uint256[] memory values,
        bytes[] memory calldatas,
        bytes32 descriptionHash
    ) internal override(Governor, GovernorTimelockControl) {
        super._executeOperations(proposalId, targets, values, calldatas, descriptionHash);
    }

    function _cancel(
        address[] memory targets,
        uint256[] memory values,
        bytes[] memory calldatas,
        bytes32 descriptionHash
    ) internal override(Governor, GovernorTimelockControl) returns (uint256) {
        return super._cancel(targets, values, calldatas, descriptionHash);
    }

    function _executor()
        internal
        view
        override(Governor, GovernorTimelockControl)
        returns (address)
    {
        return super._executor();
    }
}

3.2 时间锁控制器

// contracts/TimelockController.sol 已在OpenZeppelin中实现
// 部署时配置

import "@openzeppelin/contracts/governance/TimelockController.sol";

// 部署脚本中:
const minDelay = 2 * 24 * 60 * 60; // 2天
const proposers = [governorAddress];
const executors = [governorAddress];
const admin = adminAddress;

const timelock = await TimelockController.deploy(
    minDelay,
    proposers,
    executors,
    admin
);

4. 治理流程实现

4.1 创建提案

// scripts/create-proposal.ts
import { ethers } from "hardhat";

async function main() {
  const [proposer] = await ethers.getSigners();

  // 获取合约实例
  const governor = await ethers.getContractAt(
    "MyGovernor",
    process.env.GOVERNOR_ADDRESS!
  );

  const token = await ethers.getContractAt(
    "GovernanceToken",
    process.env.TOKEN_ADDRESS!
  );

  // 确保提案者有足够的投票权
  const proposerVotes = await token.getVotes(proposer.address);
  const proposalThreshold = await governor.proposalThreshold();

  console.log("Proposer votes:", ethers.formatEther(proposerVotes));
  console.log("Proposal threshold:", ethers.formatEther(proposalThreshold));

  if (proposerVotes < proposalThreshold) {
    throw new Error("Insufficient votes to propose");
  }

  // 定义提案内容
  // 例如:更改某个协议参数
  const treasuryAddress = process.env.TREASURY_ADDRESS!;
  const newValue = ethers.parseEther("1000");

  const targets = [treasuryAddress];
  const values = [0];
  const calldatas = [
    new ethers.Interface([
      "function updateParameter(uint256 newValue)"
    ]).encodeFunctionData("updateParameter", [newValue])
  ];
  const description = "Proposal #1: Update treasury parameter to 1000";

  // 创建提案
  console.log("Creating proposal...");
  const tx = await governor.propose(
    targets,
    values,
    calldatas,
    description
  );

  const receipt = await tx.wait();

  // 获取提案ID
  const proposalId = receipt!.logs
    .filter((log: any) => {
      try {
        const parsed = governor.interface.parseLog({
          topics: [...log.topics],
          data: log.data
        });
        return parsed?.name === "ProposalCreated";
      } catch {
        return false;
      }
    })
    .map((log: any) => {
      const parsed = governor.interface.parseLog({
        topics: [...log.topics],
        data: log.data
      });
      return parsed?.args.proposalId;
    })[0];

  console.log("Proposal created with ID:", proposalId.toString());
  console.log("Transaction hash:", receipt!.hash);

  // 保存提案信息
  const proposalInfo = {
    proposalId: proposalId.toString(),
    description,
    targets,
    values: values.map(v => v.toString()),
    calldatas,
    proposer: proposer.address,
    createdAt: new Date().toISOString(),
  };

  const fs = require("fs");
  fs.writeFileSync(
    `proposals/${proposalId.toString()}.json`,
    JSON.stringify(proposalInfo, null, 2)
  );

  console.log("Proposal info saved to:", `proposals/${proposalId.toString()}.json`);
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

4.2 投票

// scripts/vote.ts
import { ethers } from "hardhat";

enum VoteType {
  Against = 0,
  For = 1,
  Abstain = 2
}

async function main() {
  const [voter] = await ethers.getSigners();

  const governor = await ethers.getContractAt(
    "MyGovernor",
    process.env.GOVERNOR_ADDRESS!
  );

  const proposalId = process.argv[2];
  const support = parseInt(process.argv[3]) as VoteType; // 0: Against, 1: For, 2: Abstain

  if (!proposalId || support === undefined) {
    console.log("Usage: npx hardhat run scripts/vote.ts <proposalId> <support>");
    console.log("Support: 0 = Against, 1 = For, 2 = Abstain");
    return;
  }

  // 检查提案状态
  const state = await governor.state(proposalId);
  const stateNames = [
    "Pending",
    "Active",
    "Canceled",
    "Defeated",
    "Succeeded",
    "Queued",
    "Expired",
    "Executed"
  ];

  console.log("Proposal state:", stateNames[state]);

  if (state !== 1) { // Active
    throw new Error("Proposal is not active");
  }

  // 检查投票权
  const token = await ethers.getContractAt(
    "GovernanceToken",
    await governor.token()
  );

  const proposalSnapshot = await governor.proposalSnapshot(proposalId);
  const votingPower = await token.getPastVotes(voter.address, proposalSnapshot);

  console.log("Voting power:", ethers.formatEther(votingPower));

  if (votingPower === 0n) {
    throw new Error("No voting power");
  }

  // 投票
  console.log("Casting vote...");
  const tx = await governor.castVote(proposalId, support);
  await tx.wait();

  console.log("Vote cast successfully!");
  console.log("Transaction hash:", tx.hash);

  // 获取投票结果
  const proposalVotes = await governor.proposalVotes(proposalId);
  console.log("\nCurrent votes:");
  console.log("Against:", ethers.formatEther(proposalVotes.againstVotes));
  console.log("For:", ethers.formatEther(proposalVotes.forVotes));
  console.log("Abstain:", ethers.formatEther(proposalVotes.abstainVotes));
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

4.3 带理由投票

// scripts/vote-with-reason.ts
async function voteWithReason(
  governorAddress: string,
  proposalId: string,
  support: VoteType,
  reason: string
) {
  const [voter] = await ethers.getSigners();

  const governor = await ethers.getContractAt("MyGovernor", governorAddress);

  const tx = await governor.castVoteWithReason(proposalId, support, reason);
  await tx.wait();

  console.log("Vote with reason cast successfully!");
  console.log("Reason:", reason);
}

4.4 队列提案

// scripts/queue-proposal.ts
import { ethers } from "hardhat";

async function main() {
  const governor = await ethers.getContractAt(
    "MyGovernor",
    process.env.GOVERNOR_ADDRESS!
  );

  const proposalId = process.argv[2];

  if (!proposalId) {
    console.log("Usage: npx hardhat run scripts/queue-proposal.ts <proposalId>");
    return;
  }

  // 检查提案状态
  const state = await governor.state(proposalId);
  if (state !== 4) { // Succeeded
    throw new Error("Proposal has not succeeded");
  }

  // 读取提案信息
  const fs = require("fs");
  const proposalInfo = JSON.parse(
    fs.readFileSync(`proposals/${proposalId}.json`, "utf8")
  );

  const descriptionHash = ethers.id(proposalInfo.description);

  // 将提案加入队列
  console.log("Queueing proposal...");
  const tx = await governor.queue(
    proposalInfo.targets,
    proposalInfo.values,
    proposalInfo.calldatas,
    descriptionHash
  );

  await tx.wait();

  console.log("Proposal queued successfully!");
  console.log("Transaction hash:", tx.hash);
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

4.5 执行提案

// scripts/execute-proposal.ts
import { ethers } from "hardhat";

async function main() {
  const governor = await ethers.getContractAt(
    "MyGovernor",
    process.env.GOVERNOR_ADDRESS!
  );

  const proposalId = process.argv[2];

  if (!proposalId) {
    console.log("Usage: npx hardhat run scripts/execute-proposal.ts <proposalId>");
    return;
  }

  // 检查提案状态
  const state = await governor.state(proposalId);
  if (state !== 5) { // Queued
    throw new Error("Proposal is not queued");
  }

  // 读取提案信息
  const fs = require("fs");
  const proposalInfo = JSON.parse(
    fs.readFileSync(`proposals/${proposalId}.json`, "utf8")
  );

  const descriptionHash = ethers.id(proposalInfo.description);

  // 执行提案
  console.log("Executing proposal...");
  const tx = await governor.execute(
    proposalInfo.targets,
    proposalInfo.values,
    proposalInfo.calldatas,
    descriptionHash
  );

  await tx.wait();

  console.log("Proposal executed successfully!");
  console.log("Transaction hash:", tx.hash);
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

5. 高级治理功能

5.1 提案类型扩展

// contracts/AdvancedGovernor.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "./MyGovernor.sol";

contract AdvancedGovernor is MyGovernor {
    enum ProposalType {
        Standard,      // 普通提案
        Constitutional,// 宪法级提案(需要更高的quorum)
        Emergency,     // 紧急提案(更短的时间锁)
        Treasury       // 资金提案
    }

    mapping(uint256 => ProposalType) public proposalTypes;

    event ProposalTypeSet(uint256 indexed proposalId, ProposalType proposalType);

    constructor(IVotes _token, TimelockController _timelock)
        MyGovernor(_token, _timelock)
    {}

    function proposeTyped(
        address[] memory targets,
        uint256[] memory values,
        bytes[] memory calldatas,
        string memory description,
        ProposalType proposalType
    ) public returns (uint256) {
        uint256 proposalId = propose(targets, values, calldatas, description);
        proposalTypes[proposalId] = proposalType;

        emit ProposalTypeSet(proposalId, proposalType);

        return proposalId;
    }

    // 根据提案类型调整quorum
    function quorum(uint256 blockNumber)
        public
        view
        override
        returns (uint256)
    {
        // 基础quorum
        uint256 baseQuorum = super.quorum(blockNumber);

        // 可以根据提案类型调整
        // 这里简化处理,实际需要记录提案的区块号
        return baseQuorum;
    }

    // 宪法级提案需要更高的quorum
    function constitutionalQuorum(uint256 blockNumber)
        public
        view
        returns (uint256)
    {
        return super.quorum(blockNumber) * 2; // 双倍quorum
    }
}

5.2 投票策略

// contracts/voting/QuadraticVoting.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/governance/Governor.sol";

abstract contract GovernorQuadraticVoting is Governor {
    struct QuadraticVoteReceipt {
        bool hasVoted;
        uint8 support;
        uint256 votes;
        uint256 weight; // sqrt(votes)
    }

    mapping(uint256 => mapping(address => QuadraticVoteReceipt))
        private _quadraticVoteReceipts;

    function _countVote(
        uint256 proposalId,
        address account,
        uint8 support,
        uint256 weight,
        bytes memory // params
    ) internal virtual override {
        QuadraticVoteReceipt storage receipt = _quadraticVoteReceipts[proposalId][account];

        require(!receipt.hasVoted, "Already voted");

        receipt.hasVoted = true;
        receipt.support = support;
        receipt.votes = weight;

        // 二次方投票:投票权重 = sqrt(代币数量)
        receipt.weight = sqrt(weight);

        // 这里需要实现具体的计数逻辑
    }

    function sqrt(uint256 x) internal pure returns (uint256) {
        if (x == 0) return 0;

        uint256 z = (x + 1) / 2;
        uint256 y = x;

        while (z < y) {
            y = z;
            z = (x / z + z) / 2;
        }

        return y;
    }
}

5.3 委员会治理

// contracts/CommitteeGovernor.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/governance/Governor.sol";

contract CommitteeGovernor is Governor, AccessControl {
    bytes32 public constant COMMITTEE_ROLE = keccak256("COMMITTEE_ROLE");

    uint256 public committeeSize;
    uint256 public requiredApprovals;

    mapping(uint256 => mapping(address => bool)) public committeeApprovals;
    mapping(uint256 => uint256) public approvalCount;

    event CommitteeApproval(
        uint256 indexed proposalId,
        address indexed committee,
        bool approved
    );

    constructor(
        string memory name,
        uint256 _committeeSize,
        uint256 _requiredApprovals
    ) Governor(name) {
        committeeSize = _committeeSize;
        requiredApprovals = _requiredApprovals;
        _grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
    }

    function addCommitteeMember(address member) external onlyRole(DEFAULT_ADMIN_ROLE) {
        grantRole(COMMITTEE_ROLE, member);
    }

    function removeCommitteeMember(address member) external onlyRole(DEFAULT_ADMIN_ROLE) {
        revokeRole(COMMITTEE_ROLE, member);
    }

    // 委员会成员批准提案
    function approveProposal(uint256 proposalId)
        external
        onlyRole(COMMITTEE_ROLE)
    {
        require(!committeeApprovals[proposalId][msg.sender], "Already approved");

        committeeApprovals[proposalId][msg.sender] = true;
        approvalCount[proposalId]++;

        emit CommitteeApproval(proposalId, msg.sender, true);
    }

    // 重写状态检查,加入委员会批准要求
    function state(uint256 proposalId)
        public
        view
        virtual
        override
        returns (ProposalState)
    {
        ProposalState currentState = super.state(proposalId);

        // 如果提案成功,还需要检查委员会批准
        if (currentState == ProposalState.Succeeded) {
            if (approvalCount[proposalId] < requiredApprovals) {
                return ProposalState.Active; // 视为仍在进行中
            }
        }

        return currentState;
    }

    // 必需的实现
    function votingDelay() public pure override returns (uint256) {
        return 1 days;
    }

    function votingPeriod() public pure override returns (uint256) {
        return 1 weeks;
    }

    function quorum(uint256) public pure override returns (uint256) {
        return 100e18;
    }

    function _countVote(
        uint256,
        address,
        uint8,
        uint256,
        bytes memory
    ) internal pure override {
        // 简化实现
    }

    function _quorumReached(uint256) internal pure override returns (bool) {
        return true;
    }

    function _voteSucceeded(uint256) internal pure override returns (bool) {
        return true;
    }
}

6. 投票快照

6.1 链下快照投票

// contracts/SnapshotGovernance.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol";
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "@openzeppelin/contracts/utils/cryptography/EIP712.sol";

contract SnapshotGovernance is EIP712 {
    using ECDSA for bytes32;

    ERC20Votes public immutable token;

    struct Proposal {
        uint256 id;
        string ipfsHash; // 提案详情存储在IPFS
        uint256 snapshot;
        uint256 startTime;
        uint256 endTime;
        bool executed;
    }

    struct Vote {
        uint8 support;
        uint256 weight;
    }

    mapping(uint256 => Proposal) public proposals;
    mapping(uint256 => mapping(address => Vote)) public votes;
    mapping(uint256 => uint256) public forVotes;
    mapping(uint256 => uint256) public againstVotes;

    uint256 public proposalCount;

    bytes32 private constant VOTE_TYPEHASH = keccak256(
        "Vote(uint256 proposalId,uint8 support)"
    );

    event ProposalCreated(
        uint256 indexed proposalId,
        string ipfsHash,
        uint256 snapshot,
        uint256 startTime,
        uint256 endTime
    );

    event VoteCast(
        address indexed voter,
        uint256 indexed proposalId,
        uint8 support,
        uint256 weight
    );

    constructor(address _token) EIP712("SnapshotGovernance", "1") {
        token = ERC20Votes(_token);
    }

    function createProposal(
        string memory ipfsHash,
        uint256 duration
    ) external returns (uint256) {
        uint256 proposalId = ++proposalCount;
        uint256 snapshot = block.number;

        proposals[proposalId] = Proposal({
            id: proposalId,
            ipfsHash: ipfsHash,
            snapshot: snapshot,
            startTime: block.timestamp,
            endTime: block.timestamp + duration,
            executed: false
        });

        emit ProposalCreated(
            proposalId,
            ipfsHash,
            snapshot,
            block.timestamp,
            block.timestamp + duration
        );

        return proposalId;
    }

    function castVote(uint256 proposalId, uint8 support) external {
        _castVote(msg.sender, proposalId, support);
    }

    function castVoteBySig(
        uint256 proposalId,
        uint8 support,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external {
        bytes32 structHash = keccak256(abi.encode(VOTE_TYPEHASH, proposalId, support));
        bytes32 hash = _hashTypedDataV4(structHash);
        address signer = hash.recover(v, r, s);

        _castVote(signer, proposalId, support);
    }

    function _castVote(
        address voter,
        uint256 proposalId,
        uint8 support
    ) internal {
        Proposal storage proposal = proposals[proposalId];
        require(proposal.id != 0, "Proposal does not exist");
        require(block.timestamp >= proposal.startTime, "Voting not started");
        require(block.timestamp <= proposal.endTime, "Voting ended");
        require(votes[proposalId][voter].weight == 0, "Already voted");
        require(support <= 1, "Invalid support value");

        uint256 weight = token.getPastVotes(voter, proposal.snapshot);
        require(weight > 0, "No voting power");

        votes[proposalId][voter] = Vote(support, weight);

        if (support == 1) {
            forVotes[proposalId] += weight;
        } else {
            againstVotes[proposalId] += weight;
        }

        emit VoteCast(voter, proposalId, support, weight);
    }

    function getProposalResult(uint256 proposalId)
        external
        view
        returns (
            uint256 _forVotes,
            uint256 _againstVotes,
            bool passed
        )
    {
        _forVotes = forVotes[proposalId];
        _againstVotes = againstVotes[proposalId];
        passed = _forVotes > _againstVotes;
    }
}

6.2 Snapshot集成

// scripts/snapshot-integration.ts
import snapshot from "@snapshot-labs/snapshot.js";

const hub = "https://hub.snapshot.org";
const client = new snapshot.Client712(hub);

async function createSnapshotProposal(
  spaceId: string,
  proposer: string,
  title: string,
  body: string,
  choices: string[],
  startTime: number,
  endTime: number
) {
  const proposal = {
    space: spaceId,
    type: "single-choice", // or "approval", "quadratic", "ranked-choice", "weighted"
    title: title,
    body: body,
    choices: choices,
    start: startTime,
    end: endTime,
    snapshot: await getCurrentBlockNumber(),
    plugins: JSON.stringify({}),
    app: "my-dapp",
  };

  const receipt = await client.proposal(
    new ethers.Wallet(process.env.PRIVATE_KEY!),
    proposer,
    proposal
  );

  console.log("Snapshot proposal created:", receipt);
  return receipt;
}

async function voteOnSnapshot(
  spaceId: string,
  proposalId: string,
  choice: number,
  voter: string
) {
  const vote = {
    space: spaceId,
    proposal: proposalId,
    type: "single-choice",
    choice: choice,
    app: "my-dapp",
  };

  const receipt = await client.vote(
    new ethers.Wallet(process.env.PRIVATE_KEY!),
    voter,
    vote
  );

  console.log("Vote cast on Snapshot:", receipt);
  return receipt;
}

7. 国库管理

7.1 国库合约

// contracts/Treasury.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";

contract Treasury is AccessControl {
    using SafeERC20 for IERC20;

    bytes32 public constant EXECUTOR_ROLE = keccak256("EXECUTOR_ROLE");

    struct Payment {
        address recipient;
        address token;
        uint256 amount;
        uint256 executionTime;
        bool executed;
        string description;
    }

    Payment[] public payments;

    event PaymentScheduled(
        uint256 indexed paymentId,
        address indexed recipient,
        address token,
        uint256 amount
    );

    event PaymentExecuted(uint256 indexed paymentId);
    event FundsReceived(address indexed token, uint256 amount, address from);

    constructor(address executor) {
        _grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
        _grantRole(EXECUTOR_ROLE, executor);
    }

    // 接收ETH
    receive() external payable {
        emit FundsReceived(address(0), msg.value, msg.sender);
    }

    // 接收ERC20
    function depositToken(address token, uint256 amount) external {
        IERC20(token).safeTransferFrom(msg.sender, address(this), amount);
        emit FundsReceived(token, amount, msg.sender);
    }

    // 安排支付(只能由治理合约调用)
    function schedulePayment(
        address recipient,
        address token,
        uint256 amount,
        uint256 executionTime,
        string memory description
    ) external onlyRole(EXECUTOR_ROLE) returns (uint256) {
        uint256 paymentId = payments.length;

        payments.push(Payment({
            recipient: recipient,
            token: token,
            amount: amount,
            executionTime: executionTime,
            executed: false,
            description: description
        }));

        emit PaymentScheduled(paymentId, recipient, token, amount);

        return paymentId;
    }

    // 执行支付
    function executePayment(uint256 paymentId) external onlyRole(EXECUTOR_ROLE) {
        Payment storage payment = payments[paymentId];

        require(!payment.executed, "Already executed");
        require(block.timestamp >= payment.executionTime, "Too early");

        payment.executed = true;

        if (payment.token == address(0)) {
            // ETH支付
            payable(payment.recipient).transfer(payment.amount);
        } else {
            // ERC20支付
            IERC20(payment.token).safeTransfer(payment.recipient, payment.amount);
        }

        emit PaymentExecuted(paymentId);
    }

    // 查询余额
    function getBalance(address token) external view returns (uint256) {
        if (token == address(0)) {
            return address(this).balance;
        } else {
            return IERC20(token).balanceOf(address(this));
        }
    }

    // 紧急提取(多签控制)
    function emergencyWithdraw(
        address token,
        address recipient,
        uint256 amount
    ) external onlyRole(DEFAULT_ADMIN_ROLE) {
        if (token == address(0)) {
            payable(recipient).transfer(amount);
        } else {
            IERC20(token).safeTransfer(recipient, amount);
        }
    }
}

7.2 预算管理

// contracts/BudgetManager.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/access/Ownable.sol";
import "./Treasury.sol";

contract BudgetManager is Ownable {
    Treasury public immutable treasury;

    struct Budget {
        string category;
        uint256 totalAmount;
        uint256 spentAmount;
        uint256 startTime;
        uint256 endTime;
        bool active;
    }

    mapping(uint256 => Budget) public budgets;
    uint256 public budgetCount;

    event BudgetCreated(
        uint256 indexed budgetId,
        string category,
        uint256 amount,
        uint256 duration
    );

    event BudgetSpent(uint256 indexed budgetId, uint256 amount);

    constructor(address _treasury) Ownable(msg.sender) {
        treasury = Treasury(payable(_treasury));
    }

    function createBudget(
        string memory category,
        uint256 amount,
        uint256 duration
    ) external onlyOwner returns (uint256) {
        uint256 budgetId = budgetCount++;

        budgets[budgetId] = Budget({
            category: category,
            totalAmount: amount,
            spentAmount: 0,
            startTime: block.timestamp,
            endTime: block.timestamp + duration,
            active: true
        });

        emit BudgetCreated(budgetId, category, amount, duration);

        return budgetId;
    }

    function spendBudget(
        uint256 budgetId,
        address recipient,
        address token,
        uint256 amount,
        string memory description
    ) external onlyOwner {
        Budget storage budget = budgets[budgetId];

        require(budget.active, "Budget not active");
        require(block.timestamp <= budget.endTime, "Budget expired");
        require(
            budget.spentAmount + amount <= budget.totalAmount,
            "Exceeds budget"
        );

        budget.spentAmount += amount;

        // 通过国库执行支付
        treasury.schedulePayment(
            recipient,
            token,
            amount,
            block.timestamp,
            description
        );

        emit BudgetSpent(budgetId, amount);
    }

    function getRemainingBudget(uint256 budgetId)
        external
        view
        returns (uint256)
    {
        Budget storage budget = budgets[budgetId];
        return budget.totalAmount - budget.spentAmount;
    }
}

8. 完整部署流程

8.1 部署脚本

// scripts/deploy-governance.ts
import { ethers } from "hardhat";

async function main() {
  const [deployer] = await ethers.getSigners();

  console.log("Deploying governance system with account:", deployer.address);

  // 1. 部署治理代币
  console.log("\n1. Deploying Governance Token...");
  const GovernanceToken = await ethers.getContractFactory("GovernanceToken");
  const token = await GovernanceToken.deploy();
  await token.waitForDeployment();
  const tokenAddress = await token.getAddress();
  console.log("Governance Token deployed to:", tokenAddress);

  // 2. 部署时间锁
  console.log("\n2. Deploying Timelock...");
  const minDelay = 2 * 24 * 60 * 60; // 2天
  const TimelockController = await ethers.getContractFactory("TimelockController");
  const timelock = await TimelockController.deploy(
    minDelay,
    [], // proposers - will be set to governor
    [], // executors - will be set to governor
    deployer.address // admin
  );
  await timelock.waitForDeployment();
  const timelockAddress = await timelock.getAddress();
  console.log("Timelock deployed to:", timelockAddress);

  // 3. 部署Governor
  console.log("\n3. Deploying Governor...");
  const MyGovernor = await ethers.getContractFactory("MyGovernor");
  const governor = await MyGovernor.deploy(tokenAddress, timelockAddress);
  await governor.waitForDeployment();
  const governorAddress = await governor.getAddress();
  console.log("Governor deployed to:", governorAddress);

  // 4. 配置时间锁角色
  console.log("\n4. Setting up Timelock roles...");
  const proposerRole = await timelock.PROPOSER_ROLE();
  const executorRole = await timelock.EXECUTOR_ROLE();
  const adminRole = await timelock.DEFAULT_ADMIN_ROLE();

  await timelock.grantRole(proposerRole, governorAddress);
  await timelock.grantRole(executorRole, governorAddress);
  console.log("Granted proposer and executor roles to Governor");

  // 5. 部署国库
  console.log("\n5. Deploying Treasury...");
  const Treasury = await ethers.getContractFactory("Treasury");
  const treasury = await Treasury.deploy(timelockAddress);
  await treasury.waitForDeployment();
  const treasuryAddress = await treasury.getAddress();
  console.log("Treasury deployed to:", treasuryAddress);

  // 6. 分发初始代币
  console.log("\n6. Distributing initial tokens...");
  const amount = ethers.parseEther("1000");
  await token.transfer(deployer.address, amount);

  // 用户需要委托投票权
  await token.delegate(deployer.address);
  console.log("Delegated votes to deployer");

  // 7. 放弃Timelock admin权限(可选,生产环境建议)
  // await timelock.revokeRole(adminRole, deployer.address);
  // console.log("Revoked admin role from deployer");

  // 保存部署信息
  const deploymentInfo = {
    network: (await ethers.provider.getNetwork()).name,
    deployer: deployer.address,
    contracts: {
      GovernanceToken: tokenAddress,
      Timelock: timelockAddress,
      Governor: governorAddress,
      Treasury: treasuryAddress,
    },
    config: {
      minDelay: minDelay,
      votingDelay: await governor.votingDelay(),
      votingPeriod: await governor.votingPeriod(),
      proposalThreshold: await governor.proposalThreshold(),
    },
    timestamp: new Date().toISOString(),
  };

  const fs = require("fs");
  fs.writeFileSync(
    "governance-deployment.json",
    JSON.stringify(deploymentInfo, (_, v) =>
      typeof v === "bigint" ? v.toString() : v
    , 2)
  );

  console.log("\nDeployment complete!");
  console.log("Deployment info saved to governance-deployment.json");
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

9. 治理最佳实践

9.1 安全考虑

  1. 时间锁保护

    • 为重要操作设置足够的延迟时间
    • 给社区足够的时间响应恶意提案
  2. 多重签名

    • 紧急操作需要多签控制
    • 关键参数修改需要高门槛
  3. 提案审查

    • 提案前进行充分讨论
    • 技术审查机制
    • 模拟执行测试
  4. 权限管理

    • 最小权限原则
    • 角色分离
    • 定期审计

9.2 参与度优化

  1. 降低参与门槛

    • 委托机制
    • Gas补贴
    • 链下投票
  2. 激励机制

    • 投票奖励
    • 委托人奖励
    • 提案者奖励
  3. 用户体验

    • 清晰的UI/UX
    • 提案可视化
    • 实时统计数据

9.3 治理攻击防护

// 防止闪电贷治理攻击
contract FlashLoanProtectedGovernor is MyGovernor {
    // 投票权快照在提案创建时生成
    // 用户无法通过闪电贷临时增加投票权

    // 额外检查:确保投票者在快照前已持有代币
    mapping(address => uint256) public lastTokenTransfer;

    function _countVote(
        uint256 proposalId,
        address account,
        uint8 support,
        uint256 weight,
        bytes memory params
    ) internal virtual override {
        // 检查用户在提案创建前就持有代币
        uint256 proposalSnapshot = proposalSnapshot(proposalId);
        require(
            lastTokenTransfer[account] < proposalSnapshot,
            "Recent token transfer"
        );

        super._countVote(proposalId, account, support, weight, params);
    }
}

10. 总结

DAO治理是Web3的核心机制,本章涵盖了:

  1. 基础组件:治理代币、Governor合约、时间锁
  2. 治理流程:提案创建、投票、队列、执行
  3. 高级功能:多种提案类型、投票策略、委员会治理
  4. 国库管理:资金管理、预算控制
  5. 安全实践:防护机制、最佳实践

通过这些工具和模式,可以构建安全、高效、公平的去中心化治理系统。

Prev
MEV与交易优化
Next
项目实战:完整DeFi借贷协议