当前位置: 首页 > news >正文

基于以太坊的Dao治理系统

前言

今天我们基于solidity实现一个链上治理系统(On-chain Governance System)

代码

在该系统中我们创建如下几个合约:
Box.sol

一个非常简单的存储合约。

有一个私有变量 value,只能通过 store() 来修改。

修改操作只允许 合约拥有者(Owner) 调用(onlyOwner)。

每次更新时会发出事件 ValueChanged(newValue)。

用户可以通过 retrieve() 读取当前存储的值。
👉 这个合约就是未来被 治理合约控制 的目标合约。

pragma solidity ^0.8.0;import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";contract Box is Ownable {uint256 private value;// Emitted when the stored value changesevent ValueChanged(uint256 newValue);// Stores a new value in the contractfunction store(uint256 newValue) public onlyOwner {value = newValue;emit ValueChanged(newValue);}// Reads the last stored valuefunction retrieve() public view returns (uint256) {return value;}
}

GovToken.sol

这是一个 治理代币(Governance Token),继承自 OpenZeppelin 的:

ERC20:标准代币功能(转账、余额)。

ERC20Permit:支持签名授权(EIP-2612,允许 gasless approve)。

ERC20Votes:允许投票、快照,支持链上治理。

提供 mint() 函数,可以给用户铸造治理代币。
👉 代币持有者用它来 投票 或 发起提案。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {ERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/draft-ERC20Permit.sol";
import {ERC20Votes} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol";contract GovToken is ERC20, ERC20Permit, ERC20Votes {constructor() ERC20("MyToken", "MTK") ERC20Permit("MyToken") {}// The following functions are overrides required by Solidity.function mint(address to, uint256 amount) public {_mint(to, amount);}function _afterTokenTransfer(address from, address to, uint256 amount) internal override(ERC20, ERC20Votes) {super._afterTokenTransfer(from, to, amount);}function _mint(address to, uint256 amount) internal override(ERC20, ERC20Votes) {super._mint(to, amount);}function _burn(address account, uint256 amount) internal override(ERC20, ERC20Votes) {super._burn(account, amount);}
}

MyGovernor.sol

这是治理的核心合约,继承了多个 OpenZeppelin 的模块:

Governor:治理主逻辑(提案、投票、执行)。

GovernorSettings:治理参数设置,比如投票延迟、投票周期、提案门槛。

GovernorCountingSimple:投票计票方式(支持 For / Against / Abstain)。

GovernorVotes:让治理合约与 GovToken 关联。

GovernorVotesQuorumFraction:规定法定人数(投票门槛,比如总票数的 4%)。

GovernorTimelockControl:与 Timelock 结合,确保提案延迟执行。

主要参数:

votingDelay = 1:提案创建后要等待 1 个区块才可投票。

votingPeriod = 50400:大约 1 周的投票时间(假设 12s 区块时间)。

quorum = 4%:投票必须至少达到代币供应量的 4% 才有效。

它把 投票、计票、提案执行 全都整合在一起。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;import {Governor} from "@openzeppelin/contracts/governance/Governor.sol";
import {GovernorSettings} from "@openzeppelin/contracts/governance/extensions/GovernorSettings.sol";
import {GovernorCountingSimple} from "@openzeppelin/contracts/governance/extensions/GovernorCountingSimple.sol";
import {GovernorVotes} from "@openzeppelin/contracts/governance/extensions/GovernorVotes.sol";
import {GovernorVotesQuorumFraction} from"@openzeppelin/contracts/governance/extensions/GovernorVotesQuorumFraction.sol";
import {GovernorTimelockControl} from "@openzeppelin/contracts/governance/extensions/GovernorTimelockControl.sol";
import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol";import {IVotes} from "@openzeppelin/contracts/governance/utils/IVotes.sol";
import {IGovernor} from "@openzeppelin/contracts/governance/IGovernor.sol";contract MyGovernor isGovernor,GovernorSettings,GovernorCountingSimple,GovernorVotes,GovernorVotesQuorumFraction,GovernorTimelockControl
{constructor(IVotes _token, TimelockController _timelock)Governor("MyGovernor")GovernorSettings(1, /* 1 block */ 50400, /* 1 week */ 0)GovernorVotes(_token)GovernorVotesQuorumFraction(4)GovernorTimelockControl(_timelock){}// The following functions are overrides required by Solidity.function votingDelay() public view override(IGovernor, GovernorSettings) returns (uint256) {return super.votingDelay();}function votingPeriod() public view override(IGovernor, GovernorSettings) returns (uint256) {return super.votingPeriod();}function quorum(uint256 blockNumber)publicviewoverride(IGovernor, GovernorVotesQuorumFraction)returns (uint256){return super.quorum(blockNumber);}function state(uint256 proposalId)publicviewoverride(Governor, GovernorTimelockControl)returns (ProposalState){return super.state(proposalId);}function propose(address[] memory targets,uint256[] memory values,bytes[] memory calldatas,string memory description) public override(Governor, IGovernor) returns (uint256) {return super.propose(targets, values, calldatas, description);}function proposalThreshold() public view override(Governor, GovernorSettings) returns (uint256) {return super.proposalThreshold();}function _execute(uint256 proposalId,address[] memory targets,uint256[] memory values,bytes[] memory calldatas,bytes32 descriptionHash) internal override(Governor, GovernorTimelockControl) {super._execute(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 supportsInterface(bytes4 interfaceId)publicviewoverride(Governor, GovernorTimelockControl)returns (bool){return super.supportsInterface(interfaceId);}
}

TimeLock.sol

用来延迟执行治理提案的合约。

参数:

minDelay:延迟时间,必须等到时间过去后提案才能执行。

proposers:哪些地址能发起提案(一般是 Governor 合约)。

executors:哪些地址能执行提案(可以是任何人,或者也限定 Governor)。
👉 Timelock的好处是避免治理攻击,比如有人瞬间提出提案并立即执行(没有给别人反应时间)。

pragma solidity ^0.8.0;import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol";contract TimeLock is TimelockController {// minDelay is how long you have to wait before executing// proposers is the list of addresses that can propose// executors is the list of addresses that can executeconstructor(uint256 minDelay, address[] memory proposers, address[] memory executors)TimelockController(minDelay, proposers, executors, msg.sender){}
}

流程

假设用户有 100 个 GovToken → 代表你有 100 票。

用户发起一个提案:“调用 Box.store(42)”。

大家投票,提案通过。

提案进入 TimeLock,等待 1 小时。

TimeLock 执行提案 → Box.store(42) 被调用 → Box 的值更新为 42。

测试

我们通过foundry实现测试类验证流程是否正确

setUp 函数:环境搭建

function setUp() public {token = new GovToken();token.mint(VOTER, 100e18);vm.prank(VOTER);token.delegate(VOTER);

部署治理代币 GovToken 并给地址 VOTER 铸造 100 代币。

delegate → 投票权必须委托,哪怕委托给自己,否则不能投票。

timelock = new TimeLock(MIN_DELAY, proposers, executors);
governor = new MyGovernor(token, timelock);

部署时间锁 TimeLock(延迟执行治理决议)。

部署治理合约 MyGovernor。

timelock.grantRole(proposerRole, address(governor));
timelock.grantRole(executorRole, address(0));
timelock.revokeRole(adminRole, address(this));

governor 才能提出治理提案(PROPOSER_ROLE)。

executorRole = address(0) → 任何人都可以执行提案。

测试合约自己放弃 adminRole,防止作弊。

box = new Box();
box.transferOwnership(address(timelock));

部署 Box,并把它的 owner 设置为 timelock。
👉 这保证了只有治理流程批准的提案才能更新 Box。

testCantUpdateBoxWithoutGovernance 测试

vm.expectRevert();
box.store(1);

直接调用 Box.store(1) 会报错,因为 Box 的 owner 已经是 TimeLock,外部不能直接改。

testGovernanceUpdatesBox:完整治理流程

这是最核心的部分,模拟一次完整的治理提案。

1️⃣ 提案

uint256 valueToStore = 777;
string memory description = "Store 1 in Box";
bytes memory encodedFunctionCall = abi.encodeWithSignature("store(uint256)", valueToStore);
addressesToCall.push(address(box));
values.push(0);
functionCalls.push(encodedFunctionCall);uint256 proposalId = governor.propose(addressesToCall, values, functionCalls, description);

构造一个提案:调用 Box.store(777)。

governor.propose(…) 创建提案,返回一个 proposalId。

console2.log(“Proposal State:”, uint256(governor.state(proposalId))); // Pending, 0

提案状态一开始是 Pending (0)。

2️⃣ 投票

vm.warp(block.timestamp + VOTING_DELAY + 1);
vm.roll(block.number + VOTING_DELAY + 1);console2.log("Proposal State:", uint256(governor.state(proposalId))); // Active, 1

时间和区块推进,提案进入 Active (1) 状态,可以投票。

uint8 voteWay = 1; // 1 = For
vm.prank(VOTER);
governor.castVoteWithReason(proposalId, voteWay, "I like a do da cha cha");

VOTER 投票支持提案,并写入理由。

vm.warp(block.timestamp + VOTING_PERIOD + 1);
vm.roll(block.number + VOTING_PERIOD + 1);console2.log("Proposal State:", uint256(governor.state(proposalId))); // Succeeded, 4

等待投票结束 → 提案通过,状态变为 Succeeded (4)。

3️⃣ 排队(Queue)

bytes32 descriptionHash = keccak256(abi.encodePacked(description));
governor.queue(addressesToCall, values, functionCalls, descriptionHash);vm.roll(block.number + MIN_DELAY + 1);
vm.warp(block.timestamp + MIN_DELAY + 1);console2.log("Proposal State:", uint256(governor.state(proposalId))); // Queued, 5

提案进入 时间锁,状态变为 Queued (5)。

必须等 MIN_DELAY(这里是 1 小时)后才能执行。

4️⃣ 执行(Execute)

governor.execute(addressesToCall, values, functionCalls, descriptionHash);console2.log("Proposal State:", uint256(governor.state(proposalId))); // Executed, 7
assertEq(uint256(governor.state(proposalId)), 7);
assert(box.retrieve() == valueToStore);

最终执行提案,调用 Box.store(777)。

提案状态变为 Executed (7)。

断言 Box.retrieve() == 777,测试通过。
在这里插入图片描述

源码

https://github.com/Cyfrin/foundry-dao-cu

http://www.dtcms.com/a/430878.html

相关文章:

  • 【LeetCode_203】移除链表元素
  • LeetCode刷题记录----75.颜色分类
  • QQ可以在网站做临时会话么温州的网站建设公司
  • Java-Spring 入门指南(十七)SpringMVC--Apipostl与RestFul实战测试
  • Codeforces Round 993A Easy Problem
  • OSI模型、网络地址、与协议
  • Codeforces Round 993B. Normal Problem
  • 《嵌入式 – GD32开发实战指南(RISC-V版本)》第3章 GPIO流水灯的前世今生
  • 深圳手机网站建设哪家好表白链接生成器
  • GameObject 常见类型详解 -- 光环生成对象(AURA GENERATOR)
  • 29.CSS 3D 加载转轮 | CSS 动画效果
  • 潍坊制作网站用淘宝做公司网站
  • AMQP协议深度解析:消息队列背后的通信魔法
  • CSP-J/S复赛真实考试场景还原与备考策略
  • 攻防世界-Web-inget
  • flex布局学习记录
  • unordered_map和unordered_set的使用以及哈希表的实现
  • Powershell 管理 后台/计划 作业(六)
  • 北京网站建设公司东为企业网络营销方案策划书
  • 四川网站营销seo什么价格网站建设哪家g
  • k8s-pod的镜像升级与回滚
  • Django 从入门到进阶:构建完整的博客系统
  • XYplorer(多标签文件管理器) 多语便携版
  • 哈尔滨公告最新消息枣庄seo推广
  • 从输入网址到网页呈现:深入理解 HTTP 及其背后的网络世界
  • 建设一个网站需要什么软件抖音小程序在哪里找
  • Rust语言简介
  • 【无标题】Heartbeat高可用配置实践
  • 【LangChain】P6 对话记忆完全指南:从原理到实战(中)
  • 怎样才能把网站做好app开发制作软件