How to Code an On-Chain DAO

|
by: Patrick Collins

Build a decentralized governance model from scratch with hardhat, TypeScript, solidity, and Openzeppelin. This DAO (Decentralized Autonomous Organization) uses an ERC20 token with voting power to make decisions

How to build decentralized governance (a DAO)

How to build/code a DAO

Introduction

We are going to learn how to build an on-chain DAO using hardhat, solidity, typescript, and Openzeppelin. For those of you who don’t know, a DAO is a decentralized autonomous organization that is typically powered by the blockchain. You can watch my previous video overview about the DAO tooling landscape or read my previous article on How to build a DAO (High Level).

For this one, we are jumping right into the code. We will be using a 100% on-chain governance model, using an ERC20 token to vote for proposed changes. Once tooling improves for off-chain voting (using the Chainlink OCR model and a decentralized database like IPFS), we’ll probably have a tutorial to do that to save gas.

But to reiterate, be 100% sure to read my previous article on the landscape of the space before following here! Additionally, if you want to see a pythonic version with brownie, you can watch the video here, and see the code here.

Hardhat Video:

How to Code a DAO

Quickstart

The quickest way to get everything is to just do the following:

git clone https://github.com/PatrickAlphaC/dao-templatecd dao-templateyarnyarn hardhat test

And boom! You’ll run through the tests that mock proposing a vote, voting on a vote, queueing the vote, then executing!

Here is the rundown of what the test suite does:

  1. We deploy an ERC20 token that we will use to govern our DAO.
  2. We deploy a Timelock contract that we will use to give a buffer between executing proposals.

Note: The timelock is the contract that will handle all the money, ownerships, etc

3. We deploy our Governor contract

Note: The Governance contract is in charge of proposals and such, but the Timelock executes!

4. We deploy an example Box contract, which will be owned by our governance process! (aka, our timelock contract).

5. We propose a new value to be added to our Box contract.

6. We then vote on that proposal.

7. We then queue the proposal to be executed.

8. Then, we execute it!

But, let’s break it down for you…

Getting Started

It’s recommended that you’ve gone through the hardhat getting started documentation before proceeding here.

Requirements

  • git: You’ll know you did it right if you can run git --version and you see a response like git version x.x.x
  • Nodejs: You’ll know you’ve installed nodejs right if you can run node --versionand get an output like: vx.x.x
  • Yarn instead of npm: You’ll know you’ve installed yarn right if you can run:yarn --version And get an output like: x.x.x. You might need to install it with npm

What we are building

We are going to be building a DAO that uses ERC20 tokens to vote on our basic Box.sol contract that looks like this:

The beauty of how this all works is that governance is modular, and can be “stuck on” to really any contract. The key here is that our contract is “ownable”, which means that only the owner can call the store function. And the owner of this contract is going to be our DAO!

Build It

To get started, set up a TypeScript hardhat project:

mkdir dao-templatecd dao-templateyarn add hardhatyarn hardhat

And select the TypeScript option. This will create a few folders and files in your directory to play with.

In your contracts folder, create a file called Box.sol . And add the Box code you see from above, this will be the contract that we “do” governance on.

In our hardhat.config.ts we will want to update the solidity version to 0.8.12 or anything above 0.8.4 .

We’ll need to add openzeppelin contracts, and then try to compile with:

yarn add @openzeppelin/contractsyarn hardhat compile

And it should compile successfully! We have a box… Now what?

The Governance Token

Our governance token is going to be a little special, create a new file called GovernanceToken.sol in your contracts folder. It should look like this:

You’ll notice this isn’t a “normal” ERC20 token, this is because we need to keep track of “snapshots.” Whenever a vote is proposed, we want to make sure that we use people's balances from X blocks ago, instead of whenever the proposal was made. This will reduce people buying and selling voting tokens whenever they think a vote they want to be a part of is coming up and will make sure the number of votes stays consistent.

Once a “checkpoint” or a “snapshot” of people's tokens balances are calculated for a voting period, that’s it! You can’t buy more tokens after a vote is proposed and get more votes! You would have had to have already been holding the token.

We can make sure this is compiling with:

yarn hardhat compile

The Governor Contract

Arnold Schwarzenegger

Not to be confused with the governator

Now, let’s create a folder in our contracts folder called governance_standard . In the future, I want to add a governance_offchain folder, but until that Chainlink bit is in, this is what we got!

We’ll create a contract called GovernorContract.sol that looks like:

This is the contract that facilitates the voting of our GovernorToken. Here are the main functions we look at:

propose : Proposes a transaction. The propose function is modular in the sense that it allows you to call any transaction on any contract. The parameters are:

  • targets : A list of addresses you want to call some function on.
  • values : A list of ETH (or Layer 1 crypto) you want to send with your transactions to each address accordingly.
  • calldatas : A list of encoded functions and arguments of each function you want to call on each address.
  • description : The description of the proposal you’re using.

The beauty of this function is it allows you do to nearly ANYTHING across many addresses in a single transaction.

castVote : How we cast votes.

queue : Once a vote passes, we queue it to be executed.

execute : After the time lock is over, we execute the proposal.

You’ll notice that once a vote passes, it doesn’t go into effect right away, this is intentional. We want to give people time to “get out” of a protocol if they don’t like a change that is made. This “time lock” or “time out” is enforced by our “Time lock” contract… which we make next!

The Time Lock

Create a new file in the same folder as our governance contract called TimeLock.sol . This is the contract that will “own” the Box.

Note: Yes, you read that right. The TimeLock owns everything. This is because whenever our governance passes something, we want to make sure that we wait for a minimum delay before executing that function. The time lock enforces this. The governor contract will be the only contract that can ask the timelock contract to “do” stuff. And it will only be able to ask if a vote passes!

Our time lock has a few parameters:

  1. minDelay : How long we should wait between a vote passing and it executing.
  2. proposers : Who can propose transactions to the TimeLock contract (we will set it so only the governance contract can).
  3. executors : We set this up so anyone can execute a function that has passed and has waited out the time. However, this would be a perfect time to add Chainlink Keepers to make sure execution is decentralized!

And that’s all the solidity you need! Run yarn hardhat compile to compile everything!

Scripts and Tests

Now, I don’t want to dump a bunch more code into this article, but that’s the gist of it. You can see the scripts and tests in my dao-template github repo which has scripts called vote , propose , and queueAndExecute which do exactly as their names imply.

You can see the code and see exactly how to do a number of advanced solidity/hardhat concepts like:

  1. Auto-verify your smart contracts on etherscan
  2. Fast-forward time on a local network
  3. Fast-forward blocks on a local network
  4. How to encode functions and their arguments down to bytes
  5. Spin up a local hardhat node with all the contracts you like in a single command line
  6. Advanced gas reporting
  7. Typechain use

And more!

Learn More

If you’d like to keep getting the most up-to-date smart contract/blockchain/web3 developer content, be sure to follow me on Medium, Twitter, and YouTube to stay up to date.

Inspiration