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 安全考虑
时间锁保护
- 为重要操作设置足够的延迟时间
- 给社区足够的时间响应恶意提案
多重签名
- 紧急操作需要多签控制
- 关键参数修改需要高门槛
提案审查
- 提案前进行充分讨论
- 技术审查机制
- 模拟执行测试
权限管理
- 最小权限原则
- 角色分离
- 定期审计
9.2 参与度优化
降低参与门槛
- 委托机制
- Gas补贴
- 链下投票
激励机制
- 投票奖励
- 委托人奖励
- 提案者奖励
用户体验
- 清晰的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的核心机制,本章涵盖了:
- 基础组件:治理代币、Governor合约、时间锁
- 治理流程:提案创建、投票、队列、执行
- 高级功能:多种提案类型、投票策略、委员会治理
- 国库管理:资金管理、预算控制
- 安全实践:防护机制、最佳实践
通过这些工具和模式,可以构建安全、高效、公平的去中心化治理系统。