Skip to content

fricpto/foundry-DAO

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Foundry ERC20-Based DAO

This project implements a complete on-chain governance system using Foundry. It features an ERC20 governance token, a timelock for execution delay, and a governor contract to manage proposals. The system is designed to control a simple Box contract, demonstrating how the DAO can manage ownership and call functions on other smart contracts.

The repository includes a full suite of deployment scripts, interaction scripts, and comprehensive tests, including unit, fuzz, and invariant testing.

Quickstart Guide

1. 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

foundry

You'll know you did it right if you can run forge --version and you see a response like forge 0.2.0 (816e00b 2023-03-16T00:05:26.396218Z)

2. Installation & Setup

Clone the repository and install the dependencies:

git clone https://github.com/fricpto/foundry-DAO
cd foundry-DAO
forge install
forge build

3. Setup Environment Variables

You'll want to set your SEPOLIA_RPC_URL and PRIVATE_KEY as environment variables. You can add them to a .env file.

  • PRIVATE_KEY: The private key of your wallet.

    NOTE: FOR DEVELOPMENT, PLEASE USE A KEY THAT DOESN'T HAVE ANY REAL FUNDS ASSOCIATED WITH IT.

  • SEPOLIA_RPC_URL: Your RPC URL for the Sepolia testnet, which you can get for free from a node provider like Alchemy.
  • ETHERSCAN_API_KEY (Optional): Add your Etherscan API key if you want to verify your contract.

4. Get Testnet ETH and LINK

Head over to faucets.chain.link to get some testnet ETH and LINK for the Sepolia network.

Contracts Overview

Core Contracts (src/) GovToken.sol: An ERC20 token that includes ERC20Votes and ERC20Permit extensions from OpenZeppelin. This token is used for voting power, where 1 token equals 1 vote.

TimeLock.sol: A TimelockController contract that enforces a delay on all actions executed by the DAO. All successful proposals must pass through this timelock, adding a layer of security and allowing users to exit their positions if they disagree with a proposal.

MyGovernor.sol: The core governance module. It inherits from OpenZeppelin's Governor contracts to handle proposal creation, voting, and execution logic. It's configured with a voting delay, voting period, and a 4% quorum requirement.

Box.sol: A simple example contract owned by the TimeLock. The DAO can create proposals to call functions on this contract, such as store(uint256).

⚙️ Local Development & Testing Workflow

This guide walks through the entire lifecycle of a proposal on a local Anvil node.

1. Start Anvil

Open a terminal window and start a local node. Keep this running.

anvil

2. Set Environment Variables

Anvil provides a list of private keys upon startup. Choose one and set it as an environment variable in a .env file.

PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80

3. Deploy the DAO

Run the deployment script. This will set up all the necessary contracts and roles.

forge script script/Deploy.s.sol --rpc-url <your_rpc_url> --private-key <your_private_key> --broadcast

Action Required: Copy the deployed addresses for MyGovernor and Box from the output. Paste them into the constant address variables in Propose.s.sol, Vote.s.sol, Queue.s.sol, and Execute.s.sol.

4. Create a Proposal

Run the Propose script to create a new proposal to change the value in the Box contract.

forge script script/Propose.s.sol --rpc-url <your_rpc_url> --private-key <your_private_key> --broadcast

Action Required: Copy the proposalId from the output logs. Paste it into the PROPOSAL_ID constant in Vote.s.sol, Queue.s.sol, and Execute.s.sol.

5. Vote on the Proposal

The governor has a 1-block voting delay. Mine a block, then run the vote script.

Advance time by more than 1 second
cast rpc anvil_increaseTime 2
Mine 1 block to pass the voting delay
cast rpc anvil_mine

Cast a vote

forge script script/Vote.s.sol --rpc-url <your_rpc_url> --private-key <your_private_key> --broadcast

6. Advance Past the Voting Period

The voting period is set to 10 blocks. Mine 10 blocks to end the voting period.

Mine 10 blocks (0xa in hex)

cast rpc anvil_mine "0xa"

At this point, the proposal state should be Succeeded.

7. Queue the Proposal

Run the Queue script. It will verify the proposal has Succeeded and submit it to the TimeLock.

forge script script/Queue.s.sol --rpc-url <your_rpc_url> --private-key <your_private_key> --broadcast

The proposal is now Queued and waiting for the 1-second timelock delay to pass.

8. Execute the Proposal

Advance time past the timelock delay, then run the Execute script to enact the proposal.

Advance time by more than 1 second

cast rpc anvil_increaseTime 2

Mine a block to confirm the time change

cast rpc anvil_mine

Run the final execution script

forge script script/Execute.s.sol --rpc-url <your_rpc_url> --private-key <your_private_key> --broadcast

Congratulations! You should see the log message New Box value: 77. You have successfully completed a full governance cycle.

📜 Scripts Reference

Deploy.s.sol: Deploys all contracts (GovToken, TimeLock, MyGovernor, Box) and sets up initial roles.

Propose.s.sol: Creates a new proposal to call store(77) on the Box contract.

Vote.s.sol: Casts a "For" vote on the created proposal.

Queue.s.sol: Queues a succeeded proposal in the timelock, checking first that its state is Succeeded.

Execute.s.sol: Executes a queued proposal after the timelock delay has passed, checking first that its state is Queued.

✅ Testing The project includes a comprehensive test suite. Run all tests with:

forge test

Test Categories

BoxFuzzTest.t.sol: Contains unit and fuzz tests for the Box contract, ensuring its core logic and ownership rules work as expected.

MyGovernorTest.t.sol: An integration test suite for the entire governance lifecycle, covering proposal creation, voting, queuing, execution, failures, and cancellations.

Invariant Tests: These tests check that core properties of the system are never violated. They are split into two strategies based on the fail_on_revert setting in foundry.toml.

StopOnRevertInvariant/BoxInvariantTest.t.sol: To be run with fail_on_revert = false. This test allows reverts and uses vm.expectRevert to ensure that calls from non-owners fail as expected, while calls from the owner succeed.

StrictNoRevert/BoxInvariant.t.sol: To be run with fail_on_revert = true or false.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published