Skip to content

Commit

Permalink
add some readme
Browse files Browse the repository at this point in the history
  • Loading branch information
thurendous committed Sep 16, 2024
1 parent fc75aa4 commit dea6f46
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 65 deletions.
129 changes: 69 additions & 60 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,8 +169,73 @@ The function also updates the amount of the utility token that the holder has bu

### `MyGovernor.sol`

This is the smart contract for the DAO governance of the community. The contract is based on the Governor contract of OpenZeppelin.

This contract will be used in the Phase 2 of the community governance.

#### Use cases and functions explanation

- Basic governor functionality (proposal creation, voting, execution)
- Configurable governance settings (voting delay, voting period, proposal threshold)
- Integration with an ERC20Votes token for voting power
- Quorum requirements based on a fraction of total supply
- Timelock integration for delayed execution

Key features and parameters:

- Voting delay: 1 day (time between proposal creation and voting start)
- can be changed later
- Voting period: 1 week (duration of the voting phase)
- can be changed later
- Proposal threshold: 1e18 tokens (minimum tokens required to create a proposal)
- can be changed later
- Quorum: 1% of total token supply (minimum participation for a valid vote)

#### Notes

- The contract is designed to work with a specific ERC20Votes token (likely the GovToken mentioned earlier) and a TimelockController.
- Some parameters (voting delay, voting period, proposal threshold, quorum) are hardcoded in the constructor for the set up when the contract is deployed.
- The contract uses OpenZeppelin's latest contracts (v5.0.0), ensuring up-to-date security features and best practices.
- This contract is supposed to be used when the community governance is ready to be fully decentralized in the phase 2 of the community governance.

### `Timelock.sol`

The `Timelock.sol` contract is a time-lock controller based on OpenZeppelin's `TimelockController`.

This contract will be used in the Phase 2 of the community governance.

#### Use cases and functions explanation

Its main purposes and functionalities include:

1. Delayed Execution: Provides a mandatory waiting period for passed proposals, enhancing governance security.
2. Access Control: Manages which addresses can propose, execute, or cancel operations. If address(0) is set as the proposer or executor, then anyone can propose or execute.
3. Transparency: All pending operations are publicly viewable, giving community members time to review and react.

When the proposal is passed in the DaoGovernor, it will be queued in the timelock. After a predetermined delay, the actual execution is done by the timelock.

#### Notes

- The contract's admin role is set in the beginning and it is recommended to be revoked later.
- We suppose the executor role can be set as address(0) in the beginning which means anyone can execute. And the proposer role is set as the governor contract in the beginning. It means the governor contract only has the right to propose.
- Why we need to grant the timelock the minter role to mint gov token in the test case:
- When the proposal is passed, the queue operation will be called and the actual called function of timelock is `scheduledBatch()`. After that, the DaoGovernor's `execute()` function can be called by anyone to execute the proposal. And this function will call the timelock's `executeBatch()` function to execute the proposal. The real executor is the timelock.

#### Governance flow

As the code is written in the test case of using the governance to mint some gov token to user A, the flow of the governance is as follows:

Preparation:

- User A has some voting power.

1. Prepare the values, targets, calldatas, and description of the proposal.
2. After a while, User A creates the proposal: `daoGovernor.propose()`.
3. Some users cast votes for the proposal: `daoGovernor.castVote()`.
4. The proposal is queued in the timelock: `daoGovernor.queue()`.
5. After the delay, anyone can call this function: `daoGovernor.execute()`.
6. The token is minted to User A.

## About auditing

### Scope
Expand All @@ -192,7 +257,7 @@ Any file except for the files in the above Scope.
- Two step ownership transfer process is a known issue. We accept the risk of it.
- The precision loss in the calculation of the voting power and the burned token is existing but the precision loss will not be a problem because the value loss caused by it is very small, e.g. 1e9 token. It is ignorable.

### notes
### Some notes

- Because we are using a mathematical formula(mentioned in the _Reference_ section) which including square root calculation, some precision loss happens in the calculation. e.g. if we exchange 1e9 utility token for the governance token theoretically, what we get is zero token. Because of that, we made the value of the utility token to be 1e18 at least if someone wants to call the `exchange` function.
- Maybe the tests, also fuzz tests, are not able to cover all the edge cases. if you find any issues, please let us know.
Expand Down Expand Up @@ -247,11 +312,6 @@ make coverage

## References

### Documents

- [Foundry Book](https://book.getfoundry.sh/)
- [OpenZeppelin](https://docs.openzeppelin.com/)

### Mathematical Formula and tables

These are the values we used in the exchange function and tests for voting power <-> burned token calculation.
Expand Down Expand Up @@ -380,58 +440,7 @@ y: burnedToken

....

### Used Framework's documentation

https://book.getfoundry.sh/

## Usage

### Build

```shell
$ forge build
```

### Test

```shell
$ forge test
```

### Format

```shell
$ forge fmt
```

### Gas Snapshots

```shell
$ forge snapshot
```

### Anvil

```shell
$ anvil
```

### Deploy

```shell
$ forge script script/Counter.s.sol:CounterScript --rpc-url <your_rpc_url> --private-key <your_private_key>
```

### Cast

```shell
$ cast <subcommand>
```

### Help
### Documents

```shell
$ forge --help
$ anvil --help
$ cast --help
```
- [Foundry Book](https://book.getfoundry.sh/)
- [OpenZeppelin](https://docs.openzeppelin.com/)
15 changes: 10 additions & 5 deletions test/integration/DAOGovernorTest.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,12 @@ contract DAOGovernorTest is Test {
//////////////////////////////

// deploy the timelock
timelock = new Timelock(MIN_DELAY, proposers, executors, admin); // empty array means anyone can propose and anyone can execute
timelock = new Timelock(MIN_DELAY, proposers, executors, admin); // empty array means the proposer and executor are not set yet

// deploy the dao governor
daoGovernor = new DaoGovernor(govToken, timelock);

// grant the roles to the dao governor
bytes32 proposerRole = timelock.PROPOSER_ROLE(); // keccak256("PROPOSER_ROLE")
bytes32 executorRole = timelock.EXECUTOR_ROLE(); // keccak256("EXECUTOR_ROLE")
bytes32 adminRole = timelock.DEFAULT_ADMIN_ROLE(); // keccak256("DEFAULT_ADMIN_ROLE")
Expand All @@ -98,8 +100,9 @@ contract DAOGovernorTest is Test {
timelock.grantRole(proposerRole, address(daoGovernor));
// anybody can execute
timelock.grantRole(executorRole, address(0)); // anybody can execute the proposal, this should not be done in production
timelock.revokeRole(adminRole, admin); // user is the admin and we do not need a single pointo of failure
timelock.revokeRole(adminRole, admin); // admin is the admin and we do not need a single point of failure

// grant the minter role to the timelock to allow the timelock to mint the token
govToken.grantRole(govToken.MINTER_ROLE(), address(timelock));
vm.stopPrank();
}
Expand All @@ -125,19 +128,20 @@ contract DAOGovernorTest is Test {
function testGovernorCanGetProposedAndMintUtilityToken() public {
address ourAddress = makeAddr("ourAddress");
string memory description = "mint 1000e18 to our address";
bytes memory encodeFunctionData = abi.encodeWithSignature("mint(address,uint256)", ourAddress, 1000e18);
bytes memory encodeFunctionData = abi.encodeWithSignature("mint(address,uint256)", ourAddress, 1001e18);
targets.push(address(govToken)); // this is the calling address
values.push(0); // this means no value is sent to the target address
calldatas.push(encodeFunctionData); // this is the data of the function call

// check the balance and the voting power of the participant2
// check the balance and the voting power of the user
console.log(govToken.balanceOf(user));
console.log(govToken.getVotes(user));
// 1. propose to the dao
// at least 1 block later(12 seconds)
vm.warp(block.timestamp + 15);
vm.roll(block.number + 1);

// user has some voting power so he can propose
vm.startPrank(user);
uint256 proposalId = daoGovernor.propose(targets, values, calldatas, description);
vm.stopPrank();
Expand Down Expand Up @@ -172,10 +176,11 @@ contract DAOGovernorTest is Test {
// 5. execute
vm.warp(block.timestamp + MIN_DELAY + 1);
vm.roll(block.number + MIN_DELAY / 12 + 1);
// this is called but the actual execution is done by the timelock. that is why we need to grant the timelock the minter role to mint gov token.
daoGovernor.execute(targets, values, calldatas, descriptionHash);

console.log("token balance of our address: ", govToken.balanceOf(ourAddress));
assertEq(govToken.balanceOf(ourAddress), 1000e18);
assertEq(govToken.balanceOf(ourAddress), 1001e18);

assertEq(uint256(daoGovernor.state(proposalId)), uint256(IGovernor.ProposalState.Executed));
}
Expand Down

0 comments on commit dea6f46

Please sign in to comment.