如何在Solidity中建立DAO(去中心化自治组织)?
本文将帮助您理解 DAO 的概念,并帮助您构建一个基本的 DAO。
什么是 DAO?
您可以将 DAO 视为基于互联网的实体(比如企业),由其股东(拥有代币和比例投票权的成员)共同拥有和管理。在 DAO 中,决策是通过提案做出的,DAO 的成员可以对这些提案进行投票,然后执行它们。
DAO 完全由可公开查看/可验证的代码管理,没有一个人(如 CEO)负责决策。
DAO 如何运作?
如前所述,DAO 由代码管理,但是如果运行代码的机器的人决定关闭机器或编辑代码怎么办?
所需要的是让相同的代码在由不同实体托管的一组机器上运行,这样即使其中一个关闭,另一个也可以接管。区块链帮助我们解决了上述问题,基于 EVM 的区块链(如 Ethereum 和 Polygon)允许我们在公共去中心化分类账上运行智能合约。部署在这些网络上的智能合约将传播到网络上所有可以查看和验证它的节点,并且没有任何一方控制网络。
具有代币成员资格的 DAO 向其成员发行代币,代币代表系统中的投票权。根据所设置的治理,任何人都可以创建 DAO 更改提案,并将其提交给具有法定人数(通过所需的最低百分比/票数)和投票持续时间的投票。成员可以对提案进行查看和投票,投票权与成员拥有的代币数量成正比。投票期结束后,我们检查提案是否通过,如果通过,则执行。
下图显示了流程。

让我们开始建设
我们将在我们的代码库中使用OpenZeppelin合约,我还将使用 Patrick Collins 的DAO 模板中的一些代码。
先决条件
您将需要以下内容才能开始。
- Node.js :您可以从 Node.js网站下载最新版本。我在撰写本文时使用的版本是 16.14.2。
- Yarn:我们将使用 Y arn作为包管理器。
- Hardhat:我们将使用Hardhat作为本地开发环境。
资料库
我已经编写代码并将其推送到 GitHub,如果您想自己尝试,可以在此处获取代码,不过我建议您留下来,因为我会解释代码。
场景
我们将构建一个将执行以下操作的 DAO:
场景 1
- 添加初始成员。(让我们称他们为创始人)。
- 让创始人创建一个提案。(提出要在智能合约上执行的功能)。
- 让方正对上述提案进行投票,因为方正拥有 100% 的投票份额,因此它将通过。
- 执行提案。(以及智能合约中的功能)
场景 2
- 添加一个初始成员(我们称他们为 Founder)。
- 添加另一个成员并向他们发行价值 20% 的创始人份额的新代币。
- 让创始人创建一个提案(提出一个要在智能合约上执行的功能)。
- 让创始人和新成员对上述提案进行投票。法定人数设置为 90%。
- 执行提案(以及智能合约中的功能)。
合约
如前所述,我们将使用 OpenZeppelin 的治理合约。合同如下:
- Governor contract: Governor contract决定了一个quorum所需的票数/百分比(例如,如果quorum是4%,那么只需要4%的选民投票通过该提案),投票周期即多久投票是否开放,投票延迟,即提案创建后多长时间允许成员更改他们拥有的代币数量。总督还提供创建提案、投票和执行提案的功能。
- TimeLock: TimeLock 合约为不同意决定执行前退出系统的成员提供时间。
- Token: Token合约是一种特殊类型的ERC20合约,实现ERC20Votes扩展。这允许将投票权映射到过去余额的快照而不是当前余额,这有助于防止成员知道即将提出重要提案并试图通过购买更多代币然后在之后抛售来增加他们的投票权投票。
- 目标:这是合约,其代码将在提案通过投票后执行。
代码
让我们开始把这一切放在一起。使用 Hardhat 创建一个空的示例项目。在您的终端中运行以下命令。
yarn add — 开发安全帽
接下来,让我们使用 hardhat 创建我们的文件夹结构。
你应该看到这样的提示

单击“创建基本示例项目”。该过程完成后,您应该会看到类似这样的内容。

合约
让我们开始添加合约,首先让我们添加 GovernorContract。我们可以从OpenZeppelin获得相同的代码,或者您可以复制下面的代码或从我的仓库中复制代码。我的合约代码修复了 OpenZeppelin 版本中的一个问题,以及投票延迟、法定人数和投票周期的参数化,类似于 Patrick Collins 版本。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
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 GovernorContract is Governor, GovernorSettings, GovernorCountingSimple, GovernorVotes, GovernorVotesQuorumFraction, GovernorTimelockControl {
constructor(IVotes _token, TimelockController _timelock,uint256 _quorumPercentage,
uint256 _votingPeriod,
uint256 _votingDelay)
Governor(“GovernorContract”)
GovernorSettings(_votingDelay,_votingPeriod,0)
GovernorVotes(_token)
GovernorVotesQuorumFraction(_quorumPercentage)
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)
public
view
override(IGovernor, GovernorVotesQuorumFraction)
returns (uint256)
{
return super.quorum(blockNumber);
}
function getVotes(address account, uint256 blockNumber)
public
view
override(Governor, IGovernor)
returns (uint256)
{
return _getVotes(account, blockNumber, _defaultParams());
}
function state(uint256 proposalId)
public
view
override(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)
public
view
override(Governor, GovernorTimelockControl)
returns (bool)
{
return super.supportsInterface(interfaceId);
}
}
接下来,让我们添加令牌合约,这在 OpenZeppelin 上也是可用的。我的代码有一个额外的“issueToken”函数(稍后会详细介绍)。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.2;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/draft-ERC20Permit.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol";
contract MyToken is ERC20, ERC20Permit, ERC20Votes {
constructor() ERC20("MyToken", "MTK") ERC20Permit("MyToken") {
_mint(msg.sender, 1000);
}
// The functions below are overrides required by Solidity.
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);
}
function issueToken(address to, uint256 amount) public{
_mint(to, amount);
}
}
最后,让我们检查一下 Target 合约,在我们的例子中,我们将使用 Patrick Collins 使用的相同 Box 合约。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@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 execute
constructor(
uint256 minDelay,
address[] memory proposers,
address[] memory executors
) TimelockController(minDelay, proposers, executors) {}
}
测试
现在我们有了合同,我们需要编写测试。在“test”文件夹下创建一个文件“sample-test.js”。让我们开始编写我们的测试。首先,让我们使用以下数据创建一个名为“helper.config.js”的配置文件。
module.exports=
{
MIN_DELAY:3600,
QUORUM_PERCENTAGE:90,
VOTING_PERIOD:5,
VOTING_DELAY:3,
ADDRESS_ZERO :"0x0000000000000000000000000000000000000000"
}
法定人数为 90%,投票周期为 5 个区块,投票延迟为 3 个区块。TimeLock 的最小延迟为 3600 秒。
让我们编写代码将所有合约部署到本地网络(Hardhat 在内部管理它,我们不需要启动任何进程)
governanceToken = await ethers.getContractFactory("MyToken")
deployedToken=await governanceToken.deploy();
await deployedToken.deployed();
transactionResponse = await deployedToken.delegate(owner.address)
await transactionResponse.wait(1)
timeLock = await ethers.getContractFactory("TimeLock")
deployedTimeLock=await timeLock.deploy(MIN_DELAY,[],[]);
await deployedTimeLock.deployed();
governor = await ethers.getContractFactory("GovernorContract")
deployedGovernor=await governor.deploy(deployedToken.address,deployedTimeLock.address,QUORUM_PERCENTAGE,VOTING_PERIOD,VOTING_DELAY);
await deployedGovernor.deployed()
box = await ethers.getContractFactory("Box")
deployedBox=await box.deploy()
await deployedBox.deployed()
提案创建
接下来,创建提案。我们传递将在 Box 合约及其参数上调用的函数的编码值。
propose 函数的输出是一个包含Proposal Id 的交易。这用于跟踪提案。
const proposalDescription="propose this data"
let encodedFunctionCall = box.interface.encodeFunctionData("store", [77])
const proposeTx = await deployedGovernor.propose([deployedBox.address],[0],[encodedFunctionCall],proposalDescription);
提案是在值为 77 的 Box 合约上触发存储功能。
表决
然后我们对该提案进行投票,投票“1”表示同意。
注意:在这种情况下,我们只有一名成员(拥有 100% 的选票)在投票。
const voteWay = 1
const reason = "I vote yes"
let voteTx = await deployedGovernor.castVoteWithReason(proposalId, voteWay, reason)
队列和执行
接下来,来自 DAO 的任何成员都可以排队并执行该提案,如果提案通过投票,它将被执行,并且 Box 合约上的存储函数将被调用,值为 77。你可能已经注意到像moveTime和moveBlocks ,这些来自 Patrick Collins DAO 模板,在开发环境中可用于模拟时间的流逝和区块挖掘,它们帮助我们模拟投票期的完成、时间锁定延迟等。
const queueTx = await deployedGovernor.queue([deployedBox.address],[0],[encodedFunctionCall],descriptionHash)
await queueTx.wait(1)
await moveTime(MIN_DELAY + 1)
await moveBlocks(1)
console.log("Executing...")
const executeTx = await deployedGovernor.execute(
[deployedBox.address],
[0],
[encodedFunctionCall],
descriptionHash
)
await executeTx.wait(1)
const value=await deployedBox.retrieve();
console.log(value)
运行测试
我们现在可以使用以下命令运行测试
纱线安全帽测试
向新成员发行代币
我们上面看到的是场景 1 的流程。对于场景 2,我们需要向新成员发行新代币并让他们对提案进行投票。
发行代币的代码如下所示
[owner, addr1, addr2] = await ethers.getSigners();
const signer=await ethers.getSigner(addr1.address);
const deployedTokenUser2=await deployedToken.connect(signer)
await deployedTokenUser2.issueToken(addr1.address,200)
函数getSigners()返回 Hardhat 开发环境中所有帐户的列表,然后我们向该地址发行 200 个令牌。
新成员投票
现在我们有了另一个成员,我们可以用他来投票,但是新成员不能投票,除非他首先将自己添加为代币合约的代表,这样做是为了让拥有代币但不想参与决策的成员不需要花费额外的 gas 成本来维护他们在账本上的投票权快照。
自委托的代码如下。
[owner, addr1, addr2] = await ethers.getSigners();
const signer=await ethers.getSigner(addr1.address);
const deployedTokenUser2=await deployedToken.connect(signer)
await deployedTokenUser2.issueToken(addr1.address,200)
通过Github 获取更多区块链学习资料!
https://github.com/Manuel-yang/BlockChainSelfLearning
如何在Solidity中建立DAO(去中心化自治组织)?的更多相关文章
- NGK的去中心化自治实践,更人性化的DAO
2020年,DeFi市场市场火爆的同时,引爆了流动性挖矿的市场.行业内对DAO的思考也在源源不断进行,特别项目治理通证发行之前,DAO的去中心化的治理理念,是区块链属性中的重要的一环,也已引发了不同项 ...
- NGK DeFi项目即将上线,打造去中心化闭环金融生态!
据最新官方消息称:NGK已于近日宣布将进军DeFi领域,NGK此次的DeFi的项目将会是一个去中心的交易平台,其最大的功能是进行数字货币的交换.在用户选择了需要支付的数字货币和想购买的数字货币后,系统 ...
- eos中BM与有BM特色的去中心化。区块链世界,白皮书为共识,代码为法律。
比特币挖矿是谁算力高,谁更容易挖到新的比特币,而BM认为这太浪费资源了,于是设计了DPoS:在DPoS系统里,大家不再挖矿.而是指定几个人负责记账,不叫矿工,而叫见证人.比特股里开始是101人,EOS ...
- serf 中去中心化系统的原理和实现
原文:https://www.infoq.cn/article/principle-and-impleme-of-de-centering-system-in-serf serf 是出自 Hashic ...
- 一步步教你开发、部署第一个去中心化应用(Dapp) - 宠物商店
今天我们来编写一个完整的去中心化(区块链)应用(Dapps), 本文可以和编写智能合约结合起来看. 写在前面 阅读本文前,你应该对以太坊.智能合约有所了解,如果你还不了解,建议你先看以太坊是什么除此之 ...
- 小众Tox——大众的“去中心化”聊天软件
★Tox是什么 一个反窥探的开源项目:一种基于DHT(BitTorrent)技术的即时通讯协议:一个为安全而生的加密通讯系统 .美国棱镜计划曝光后,一个名为 irungentoo 的牛人于17天后的2 ...
- ImCash:币安下架BSV之辩:规则、中立与去中心化
一种看法是:一个引用价格数据和执行交易的加密货币交易所,其业务决策往往是在链外发生的,不受制于严格的.类似于准宪法的链上规则的约束,加密货币交易所可以拒绝任何人喜欢的价格和交易,而且这样做并不会损害底 ...
- 理解去中心化 稳定币 DAI
本文转载于深入浅出区块链, 原文链接 随着摩根大通推出JPM Coin 稳定币,可以预见稳定币将成为区块链落地的一大助推器. 坦白来讲,对于一个程序员的我来讲(不懂一点专业经济和金融),理解DAI的机 ...
- 去中心化存储项目终极指南 | Filecoin, Storj 和 PPIO 项目异同
Filecoin,Storj 以及 PPIO 这三个存储公链的设计思路是不一样的,没有优劣之分,写这篇文章也并不是为了争论各项目的好坏对错.去中心化存储是一个长期商业赛道,不同团队在同一个赛道上往不同 ...
- 谈互联网开放平台:“去中心化”大势所趋 zz
文/磐石之心 几天前与好友聊到众筹咖啡馆的事情,他向我讲述了一个非常具有特色的众筹咖啡馆案例.而这个案例也引发我对当前互联网开放.去中心和集权的一些思考,今天就简单写出来与大家分享. 一个无赚钱目的的 ...
随机推荐
- 【逆向】x64dbg设置条件断点 比较内存字符串是否相等
前言 在OD中可以设置条件断点,通过表达式对字符串数据进行比较,比如在CreateFile打开某个特定文件的时候让调试器中断.但是在x32dbg.x64dbg中因为表达式只支持整数,不支持字符串和其它 ...
- 【alive-progress】Python控制台输出动态进度条
简介 alive-progress是一种具有实时吞吐量和非常酷的动画新型的进度条python库. 使用 from alive_progress import alive_bar import time ...
- NPM镜像代理设置
用户目录C:\Users\xxx下建立.npmrc文件,内容如下: registry="https://registry.npm.taobao.org" ELECTRON_MIRR ...
- vue移动端登录与登录保持
成品效果 首先进入首页点击右下角个人中心,若状态为登录中则进入个人中心页面,否则进入登录页 实现步骤 首先完成静态的登录页与个人中心页面 登录页 <template> <div cl ...
- antv g6 出现 n.addEdge is not a function问题
问题描述直接上图 解决方式就是将edge里面边的source和target对应的id换成字符串类型就行. 例如: edges: [ { id: 299, source": 3629.toSt ...
- IT工具知识-11:一种安卓投屏到Win10失败的解决方法
软硬件平台 电脑:WIN10 LTSC 手机:红米K30Pro/MIUI 11.0.26 投屏软件:安卓端-自带投屏,WIN10-自带投屏(连接) 故障描述 之前还能用的,但是在换了个路由器之后就不能 ...
- swagger 兼容 docker 转发 配置
app.UseSwagger(c => { c.PreSerializeFilters.Add((swagger, httpReq) => { string swagger_index_u ...
- 纯js实现字符串formate方法
function format(pattern){ if(! (pattern instanceof String)){ throw new TypeError("错误的参数类型" ...
- C#向其实进程子窗体发送指令
近日,想在自己的软件简单控制其它软件的最大化最小化,想到直接向进程发送指令,结果一直无效,经过Spy++发现,原来快捷方式在子窗体上,所以需要遍历子窗体在发送指令,以下为参考代码: 1 [DllImp ...
- GO语言http请求方法,可以携带请求头Header与cookie
1.目录 2.main.go package main import "fmt" import "demo/common/http" func main() { ...