前言继上一篇DAO基础理论与代码落地的讲解,本文继续深入DAO2.0核心理论,并带来可直接落地的代码实践,带你完成从基础到进阶的技术跨越。概述DAO1.0与DAO2.0的核心区别在于从“初级的自动化投票”进化到“复杂的治理与流动性管理”。DAO1.0解决了基础的组织去中心
继上一篇 DAO 基础理论与代码落地的讲解,本文继续深入DAO2.0核心理论,并带来可直接落地的代码实践,带你完成从基础到进阶的技术跨越。
概述
DAO 1.0 与 DAO 2.0 的核心区别在于从“初级的自动化投票”进化到“复杂的治理与流动性管理”。DAO 1.0 解决了基础的组织去中心化问题,而 DAO 2.0 致力于解决治理低效、流动性不可持续以及跨组织协作等深层次挑战
DAO1.0 vs DAO2.0关键差异
1. 核心定义与重心
| 维度 | DAO 1.0 | DAO 2.0 |
|---|---|---|
| 治理重心 | 基础投票与资金拨付 | 动态自适应治理、算法审计 |
| 流动性控制 | 参与者持有(不稳定) | 协议自身持有(POL,可持续) |
| 协作范围 | 内部成员交互 | 跨组织 (DAO2DAO) 协作 |
| 解决痛点 | 中心化权威问题 | 治理效率低、流动性不稳、高成本 |
| 典型特征 | “代码即法律” | “管理之上的管理” |
这是 DAO 2.0 最早的爆发点(源于 DeFi 2.0 运动)。
DAO 1.0 是个人与组织的交互,2.0 开启了组织间的商业协作。
解决大型 DAO 治理低效(所有人投所有票)的问题。
将治理从“纯人为判断”升级为“算法触发”。
随着多链生态发展,DAO 必须具备跨链指挥权。
场景:治理提案在以太坊主网发起并投票,执行结果通过跨链桥(如 LayerZero, Axelar)自动触发 Layer 2 或其他公链上的合约修改。
落地案例:Uniswap。它的治理依然在 L1,但可以通过跨链指令管理部署在 Polygon、Arbitrum 等链上的协议参数。
| 特性 | DAO 1.0 表现 | DAO 2.0 表现 |
|---|---|---|
| 金库管理 | 闲置资产,仅用于拨款 | 积极投资、POL、流动性管理 |
| 执行效率 | 依赖多签,人工操作多 | 时间锁自动化、算法触发执行 |
| 组织结构 | 扁平化,决策拥堵 | 层级化(子 DAO)、模块化治理 |
| 激励机制 | 简单的代币奖励 | 动态归属(Vesting)、基于贡献的声誉系统 |
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
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/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";
import "@openzeppelin/contracts/governance/TimelockController.sol";
contract MyToken is ERC20, ERC20Permit, ERC20Votes {
constructor() ERC20("Governance Token", "GT") ERC20Permit("Governance Token") {
_mint(msg.sender, 1000000 * 10**decimals());
}
// 解决 ERC20Permit 和 Governor(Nonces) 的冲突
function nonces(address owner) public view override(ERC20Permit, Nonces) returns (uint256) {
return super.nonces(owner);
}
function _update(address from, address to, uint256 value) internal override(ERC20, ERC20Votes) {
super._update(from, to, value);
}
}
// 包装一下,让 Hardhat 能够识别并编译它
contract MyTimelock is TimelockController {
constructor(
uint256 minDelay,
address[] memory proposers,
address[] memory executors,
address admin
) TimelockController(minDelay, proposers, executors, admin) {}
}
contract MyGovernor is
Governor,
GovernorSettings,
GovernorCountingSimple,
GovernorVotes,
GovernorVotesQuorumFraction,
GovernorTimelockControl
{
constructor(IVotes _token, TimelockController _timelock)
Governor("MyDAO_2.0")
GovernorSettings(1, 10, 0)
GovernorVotes(_token)
GovernorVotesQuorumFraction(4)
GovernorTimelockControl(_timelock)
{}
// --- 核心修复:简化 override 列表,只保留基类 Governor ---
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 proposalThreshold() public view override(Governor, GovernorSettings) returns (uint256) {
return super.proposalThreshold();
}
// 这里是报错的关键:简化为 override(Governor)
function supportsInterface(bytes4 interfaceId) public view override(Governor) returns (bool) {
return super.supportsInterface(interfaceId);
}
// V5 内部执行逻辑
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 _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 _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();
}
function proposalNeedsQueuing(uint256 proposalId) public view override(Governor, GovernorTimelockControl) returns (bool) {
return super.proposalNeedsQueuing(proposalId);
}
}
测试用例说明
import assert from "node:assert/strict";
import { describe, it, beforeEach } from "node:test";
import { network } from "hardhat";
import { keccak256, encodePacked, encodeFunctionData, decodeEventLog, zeroAddress } from 'viem';describe("DAO 2.0 Governance Lifecycle (V5 + Viem)", function () { let token: any; let timelock: any; let governor: any; let publicClient: any; let testClient: any; let deployer: any, voter: any, proposer: any;
beforeEach(async function () {
const { viem } = await (network as any).connect();
publicClient = await viem.getPublicClient();
testClient = await viem.getTestClient();
[deployer, voter, proposer] = await viem.getWalletClients();
// --- 修复 1: 使用完全限定名 (Fully Qualified Name) 解决 HHE1001 ---
token = await viem.deployContract("contracts/DAO2.0.sol:MyToken", []);
// Timelock 通常在库里,如果没有重名可以直接用,稳妥起见也可以指定
timelock = await viem.deployContract("contracts/DAO2.0.sol:MyTimelock", [
0n,
[],
[],
deployer.account.address
]);
governor = await viem.deployContract("contracts/DAO2.0.sol:MyGovernor", [
token.address,
timelock.address
]);
// --- 核心修复点:分配足够的代币以满足 Quorum (4%) ---
// 假设总供应量是 1,000,000。4% 是 40,000。
// 我们给 voter 分配 100,000 (10%) 确保绝对通过。
const amount = 100000n * 10n ** 18n;
await token.write.transfer([voter.account.address, amount]);
// 关键:必须在 delegate 后推进区块,Governor 才会记录快照
await token.write.delegate([voter.account.address], { account: voter.account });
// 给金库充钱(供提案拨付用)
await token.write.transfer([timelock.address, amount]);
// 推进区块以确保 delegate 产生的快照被 Governor 识别
await testClient.mine({ blocks: 5 });
// 4. 权限设置 (保持不变)
const PROPOSER_ROLE = keccak256(encodePacked(['string'], ['PROPOSER_ROLE']));
const EXECUTOR_ROLE = keccak256(encodePacked(['string'], ['EXECUTOR_ROLE']));
await timelock.write.grantRole([PROPOSER_ROLE, governor.address]);
await timelock.write.grantRole([EXECUTOR_ROLE, zeroAddress]);
});
it("应该完成从提案发起、投票、进入时间锁到成功拨付资金的完整 2.0 流程", async function () {
const calldata = encodeFunctionData({
abi: token.abi,
functionName: 'transfer',
args: [proposer.account.address, 100n * 10n ** 18n]
});
const description = "DAO 2.0: Grant Funding";
const descHash = keccak256(encodePacked(['string'], [description]));
// 1. Propose
const txHash = await governor.write.propose([
[token.address], [0n], [calldata], description
], { account: proposer.account });
const receipt = await publicClient.waitForTransactionReceipt({ hash: txHash });
// --- 修复 2: 健壮的事件解码 (适配 V5 多个 Log 的情况) ---
const event = decodeEventLog({
abi: governor.abi,
eventName: 'ProposalCreated',
// 遍历 logs 找到对应的事件
...receipt.logs.find((log: any) => {
try {
const decoded = decodeEventLog({ abi: governor.abi, ...log });
return decoded.eventName === 'ProposalCreated';
} catch { return false; }
})
});
const proposalId = (event.args as any).proposalId;
// 2. Vote
await testClient.mine({ blocks: 2 });
await governor.write.castVote([proposalId, 1], { account: voter.account });
// 3. Queue
await testClient.mine({ blocks: 15 });
const currentState = await governor.read.state([proposalId]);
console.log("Proposal State after voting:", currentState); // 应该输出 4 (Succeeded)
assert.equal(Number(currentState), 4, "提案应当成功通过,而不是 Defeated");
await governor.write.queue([[token.address], [0n], [calldata], descHash]);
// 4. Execute
await governor.write.execute([[token.address], [0n], [calldata], descHash]);
// 验证
const balance = await token.read.balanceOf([proposer.account.address]);
assert.equal(balance, 100n * 10n ** 18n, "资金应由 Timelock 拨付成功");
});
it("当赞成票未达标时提案应被拒绝 (Defeated)", async function () {
const tx = await governor.write.propose([[token.address], [0n], ['0x'], "Fail Test"]);
const receipt = await publicClient.waitForTransactionReceipt({ hash: tx });
const event = decodeEventLog({
abi: governor.abi, eventName: 'ProposalCreated',
...receipt.logs.find((log: any) => log.topics[0] === keccak256(encodePacked(['string'], ['ProposalCreated(uint256,address,address[],uint256[],string[],bytes[],uint256,uint256,string)'])))
});
const proposalId = (event.args as any).proposalId;
await testClient.mine({ blocks: 2 });
await governor.write.castVote([proposalId, 0], { account: voter.account });
await testClient.mine({ blocks: 15 });
const state = await governor.read.state([proposalId]);
assert.equal(Number(state), 3, "状态应为 Defeated");
});
});
### 部署脚本
// scripts/deploy.js import { network, artifacts } from "hardhat"; async function main() { // 连接网络 const { viem } = await network.connect({ network: network.name });//指定网络进行链接
// 获取客户端 const [deployer] = await viem.getWalletClients(); const publicClient = await viem.getPublicClient();
const deployerAddress = deployer.account.address; console.log("部署者的地址:", deployerAddress); // 加载合约 const TokenArtifact = await artifacts.readArtifact("contracts/DAO2.0.sol:MyToken"); const GovernorArtifact = await artifacts.readArtifact("contracts/DAO2.0.sol:MyGovernor"); const TimelockArtifact = await artifacts.readArtifact("contracts/DAO2.0.sol:MyTimelock"); const TokenHash = await deployer.deployContract({ abi: TokenArtifact.abi,//获取abi bytecode: TokenArtifact.bytecode,//硬编码 args: [], }); const TokenReceipt = await publicClient.waitForTransactionReceipt({ hash: TokenHash }); console.log("Token合约地址:", TokenReceipt.contractAddress);
const TimelockHash = await deployer.deployContract({ abi: TimelockArtifact.abi,//获取abi bytecode: TimelockArtifact.bytecode,//硬编码 args: [0n, [], [], deployerAddress], }); const TimelockReceipt = await publicClient.waitForTransactionReceipt({ hash: TimelockHash }); console.log("Timelock合约地址:", TimelockReceipt.contractAddress); // 部署 const GovernorHash = await deployer.deployContract({ abi: GovernorArtifact.abi,//获取abi bytecode: GovernorArtifact.bytecode,//硬编码 args: [TokenReceipt.contractAddress, TimelockReceipt.contractAddress], }); const GovernorReceipt = await publicClient.waitForTransactionReceipt({ hash: GovernorHash }); console.log("Governor合约地址:", GovernorReceipt.contractAddress); }
main().catch(console.error);
# 结语
至此,本文已完成 DAO2.0 从理论到代码落地的全流程讲解,并系统对比了 DAO1.0 与 DAO2.0 的核心差异。希望能为大家理解与实践新一代 DAO 提供清晰参考。 如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!