本文将帮助我们理解DAO的概念,并帮助我们构建基本DAO。
本文将帮助我们理解DAO的概念,并帮助我们构建基本DAO。
我们可以将DAO看作是基于互联网的实体(比如企业),由其股东(持有代币和比例投票权的成员)集体拥有和管理。在DAO中,决策是通过提案做出的,DAO的成员可以对这些提案进行投票,然后执行它们。
DAO完全由公开可见/可验证的代码管理,没有单独的个人(比如CEO)负责决策制定。
如前所述,DAO由代码控制,但如果运行代码机器的人决定关闭机器或编辑代码,该怎么办呢?
我们需要的是让相同的代码运行在由不同实体托管的一组机器上,这样即使其中一个关闭了,另一个也可以接管。区块链帮助我们解决上述问题,基于EVM的区块链,如以太坊和Polygon,允许我们在公共去中心化账本上运行智能合约。部署在这些网络上的智能合约将传播到网络上的所有节点,这些节点可以查看和验证它,没有任何一方控制网络。
DAO向其成员发放代币,代币代表系统中的投票权。根据所设置的治理,任何人都可以创建对DAO进行更改的提案,并将其提交给规定人数(通过所需的最小百分比/投票数),并在投票时间里进行投票。成员可以对提案进行查看和投票,投票权与成员拥有的代币数量成正比。一旦投票期结束,我们检查提案是否通过,如果通过,则执行。
例如MakerDAO和Aragon。
下图显示了该流程。
接下来,我们将构建自己的DAO。
我们将在代码库中使用OpenZeppelin合约,我还将使用来自Patrick Collins的DAO模板的一些代码。
我们需要以下工具。
我们将构建一个DAO,它将做以下工作:
场景1
场景2
如前所述,我们将使用OpenZeppelin的治理合约。合约内容如下:
让我们开始把这些放在一起。使用Hardhat创建一个空的示例项目。在终端中运行如下命令。
Yarn add - dev hardhat
接下来,让我们使用hardhat创建我们的文件夹结构。
yarn hardhat
我们应该会看到这样的提示。
点击“创建基本示例项目”。该过程完成后,我们应该会看到如下内容。
如果你是复制了我的代码,那么可以直接跳过上述步骤。
让我们开始添加合约,首先我们添加GovernorContract。我们可以从OpenZeppelin获得相同的代码,或者我们可以复制下面的代码或从我的repo中复制。我的合约代码修复了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);
}
}
接下来是TimeLock合约,这是DAO模板中的合约副本。
// 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) {}
}
最后,让我们检查Target合约,在我们的例子中,我们将使用Patrick Collins使用的同一个Box合约。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/access/Ownable.sol";
contract Box is Ownable {
uint256 private value;
// Emitted when the stored value changes
event ValueChanged(uint256 newValue);
// Stores a new value in the contract
function store(uint256 newValue) public onlyOwner {
value = newValue;
emit ValueChanged(newValue);
}
// Reads the last stored value
function retrieve() public view returns (uint256) {
return value;
}
}
在“test”文件夹下创建一个文件sample-test.js。让我们开始编写测试。首先,让我们用以下数据创建一个名为“helper.config.js”的配置文件。
module.exports=
{
MIN_DELAY:3600,
QUORUM_PERCENTAGE:90,
VOTING_PERIOD:5,
VOTING_DELAY:3,
ADDRESS_ZERO :"0x0000000000000000000000000000000000000000"
}
Quorum为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()
接下来,我们需要将部署的Target合约(Box)的所有权转移到TimeLock合约。这样做是为了TimeLock将有权对Box合约执行操作。
const transferTx = await
deployedBox.transferOwnership(deployedTimeLock.address):
接下来,Governor合约被授予提案者角色,执行角色被授予“零地址”,这意味着任何人都可以执行提案。
const proposerRole = await deployedTimeLock.PROPOSER_ROLE()
const executorRole = await deployedTimeLock.EXECUTOR_ROLE()
const adminRole = await deployedTimeLock.TIMELOCK_ADMIN_ROLE()
const proposerTx = await deployedTimeLock.grantRole(proposerRole, deployedGovernor.address)
await proposerTx.wait(1)
const executorTx = await deployedTimeLock.grantRole(executorRole, ADDRESS_ZERO)
await executorTx.wait(1)
const revokeTx = await deployedTimeLock.revokeRole(adminRole, owner.address)
await revokeTx.wait(1)
接下来,创建提案。我们传递将在 Box 合约上调用的函数的编码值及其参数。
Proposal函数的输出是一个包含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 合约上触发store功能。
我们对提案进行投票,投票“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)
运行测试
我们现在可以使用以下命令运行测试:
yarn hardhat test
对于场景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个代币。
现在我们是另一个成员,我们可以使用他来投票,但是新成员不能投票,除非他先将自己添加为 Token 合约的代表,这样做是为了让拥有代币但不想参与决策的成员不需要花费额外的 gas 成本来维护他们在账本上的投票权快照。
自我授权的代码如下所示。
const voteWay = 1
const reason = "I vote yes"
const deployedGovernorUser2=await deployedGovernor.connect(signer)
voteTx = await deployedGovernorUser2.castVoteWithReason(proposalId, voteWay, reason)
ChinaDeFi - ChinaDeFi.com 是一个研究驱动的DeFi创新组织,同时我们也是区块链开发团队。每天从全球超过500个优质信息源的近900篇内容中,寻找思考更具深度、梳理更为系统的内容,以最快的速度同步到中国市场提供决策辅助材料。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!