DAOs are an effective and safe way to work with like-minded folks around the globe. DAOs are like digital organizations with a bank account. This could be the next big trend in crypto, and they could be the future of companies
Why do we need DAOs?
Starting an organization with someone that involves funding and money requires a lot of trust in the people you’re working with. But it’s hard to trust someone you’ve only ever interacted with on the internet. With DAOs you don’t need to trust anyone else in the group, just the DAO’s code, which is 100% transparent and verifiable by anyone.
This opens up so many new opportunities for global collaboration and coordination.
Token-based membership
Usually fully permissionless, depending on the token used. Mostly these governance tokens can be traded permissionlessly on a decentralized exchange. Others must be earned through providing liquidity or some other ‘proof-of-work’. Either way, simply holding the token grants access to voting.
Typically used to govern broad decentralized protocols and/or tokens themselves.
MakerDAO – MakerDAO’s token MKR is widely available on decentralized exchanges. So anyone can buy into having voting power on the Maker protocol’s future.
Setup
We’ll be using hardhat framework for this; to get started create a folder and run
npm init --yes
npm install --save-dev hardhat
In the same directory where you installed Hardhat run:
npx hardhat
And select create an empty hardhat.config.js
Finally, install ethers.js
and waffle
npm install --save-dev @nomiclabs/hardhat-ethers ethers @nomiclabs/hardhat-waffle ethereum-waffle chai
Add the highlighted line to the top of hardhat.config.js
:
require("@nomiclabs/hardhat-waffle");
Start by creating a new directory called contracts
and create a file inside the directory called GovernanceToken.sol
We’ll use the openzeppelin for our token, so install it using
npm install --save-dev @openzeppelin/contracts
Smart Contracts
Next, we’ll create our governance token with a max supply of 1000
and override some of the functions that are required.
Now, let’s create a protocol we want to govern using this token. That’s this really simple box
contract, that’s going to be owned by the DAO that we create.
lets create a function called store
that can be called by the DAO to change state variable.
// contracts/Box.sol
// 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;
}
}
Next, let’s create our governance contracts, so inside a folder called Governance
create a file called GovernanceContract.sol
this contract will hold all the logic for our DAO.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/governance/Governor.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 "../GovernanceToken.sol";
contract GovernorContract is
Governor,
GovernorCountingSimple,
GovernorVotes,
GovernorVotesQuorumFraction,
GovernorTimelockControl
{
uint256 public s_votingDelay;
uint256 public s_votingPeriod;
constructor(
GovernanceToken _token,
TimelockController _timelock,
uint256 _quorumPercentage,
uint256 _votingPeriod,
uint256 _votingDelay
)
Governor("GovernorContract")
GovernorVotes(_token)
GovernorVotesQuorumFraction(_quorumPercentage)
GovernorTimelockControl(_timelock)
{
s_votingDelay = _votingDelay;
s_votingPeriod = _votingPeriod;
}
function votingDelay() public view override returns (uint256) {
return s_votingDelay; // 1 = 1 block
}
function votingPeriod() public view override returns (uint256) {
return s_votingPeriod; // 45818 = 1 week
}
// The following functions are overrides required by Solidity.
function quorum(uint256 blockNumber)
public
view
override(IGovernor, GovernorVotesQuorumFraction)
returns (uint256)
{
return super.quorum(blockNumber);
}
function getVotes(address account, uint256 blockNumber)
public
view
override(IGovernor, GovernorVotes)
returns (uint256)
{
return super.getVotes(account, blockNumber);
}
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 _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);
}
}
You can use the openzepplin wizard to customize your governancecontract
Lastly we need to create a GovernanceTimelock.sol
this is the contract that’s is going to own our protocol including the funds in our DAO.
It’s going to need 3 input parameters: a minimum delay is how long you are going to wait before executing, proposers are list of members who can participate in voting and executor is list of addresses that are going to execute proposals.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/governance/TimelockController.sol";
contract GovernanceTimeLock 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) {}
}
Back to the GovernanceContract.sol
, the constructor takes 2 parameter: the Governance we created to asser the voting power and GovernanceTimelock
instance to manage our DAO.
Now to compile run:
npx hardhat compile
Conclusion
So that’s it for this coding tutorial of a DAO.
If you want to know more about DAOs checkout this video where Julien explains how DAOs work.
Leave a Reply