An extension that introduces OpenZeppelin's AccessControl restrictions to the governance processes of Governor derived contracts.
Explore the docs »
View an example contract on sepolia.etherscan.io.
·
Report Bug
·
Request Feature
Table of Contents
The extension restricts access to governance processes along restricted roles.
It does this by relating the AccessControl
to the Governor contract
by restricting who can propose, vote on and execute proposals along the access control of an external function.
Why do this?
- It enables the separation of powers in a DAO along access roles.
- This in turn enables the creation of checks and balances between these roles.
- This is a tried and true approach to safeguarding decentralisation of (social, political and economic) assets in light of their tendency to centralise around informal elites.
How does it work?
- It has to be used with an additional contract that consists of functions that are set as
restricted
and, optionally, set asonlyGovernance
. - Proposing, voting and executing proposals always happen in relation to calling these external restricted function.
- The governance process is restricted along the role restrictions of these external functions.
- For example: When an external function is restricted to
JUDGE_ROLE
, then it is only possible for those holding theJUDGE_ROLE
to propose, vote on and execute proposals related to this fucntion.
See the following diagram:
All solidity contracts can be found in the src
folder. The folder consists of the following subfolders and files.
governor-extensions
GovernorDividedPowers.sol
: the extension to OpenZeppelin'sgovernor
contract. Overrides the_propose
,_countVote
and_executeOperations
functions. It aims to keep breaking changes to other extensions to a minimum. It incorporates a simple vote counting system based on theGovernorCountingVoteSimple.sol
contract.
example-laws
LawTemplate.sol
: A base contract for functions to use withGovernorDividedPowers.sol
. Needs to be inherited by contracts that will be called by theGovernor
contract.LawsAdministrative.sol
: Example function that regulate checks and balances between roles. These all take proposalId from other roles as input variables, to allow their decisions to be checked by a secondary role. IRL, these are called administrative laws.LawsElectoral.sol
: Example functions for the selection and deselection of accounts to specific roles. Although often not involving a democratic election, these are laws that 'elect' accounts to particular roles.LawsPublic.sol
: These are any kind of function that does not fall into the electoral or administratice bracket. They are usually functions that guide the functioning of the DAO and related smart contracts.
example-governance-system
GovernedIdentity.sol
: An example implementation of theGovernorDividedPowers.sol
extension. It builds on the contracts from thegovernor-extension
andexample-laws
folders.
- Solidity 0.8.24
- Foundry 0.2.0
- OpenZeppelin 5.0.2
To get a local copy up and running do the following.
- Install git
- You'll know you did it right if you can run
git --version
and you see a response likegit version x.x.x
- You'll know you did it right if you can run
- Install foundry
- You'll know you did it right if you can run
forge --version
and you see a response likeforge 0.2.0 (816e00b 2023-03-16T00:05:26.396218Z)
- You'll know you did it right if you can run
-
Clone the repo
git clone https://github.com/7Cedars/governor-restricted-powers
-
Run make
cd governor-restricted-powers make
-
Run tests
forge test
-
Build contracts
forge build
-
This protocol is under active development. Basic functionality is incomplete.
-
Combining the AccessControl and Governor contracts results in several inconsistencies. Both contracts have their own governance logics and protocol infrastructure. The former revolves around designating and revoking roles that give access to functions; while the latter revolves around proposing and voting for proposals that give access to function execution. As a result
- Role restrictions of external functions are set from the governance contract, which is a workflow that invites for setting role restrictions incorrectly. They should be set within the functions themselves.
- There are three different execute functions. One relates to the execution of a proposal (inherited from Governor) while the other two relate to executing an external function (inherited from AccessManager).
- There are 89 public functions in total. It is excessive and invites for bugs and security risks.
- The only way to solve these issues is to create an entirely new governance protocol.
-
The
GovernorDividedPowers
implies two different levels at which votes are cast: A first in which individual members are selected for roles and a second in which role holders vote on proposals. These two levels have different voting logics.- The first can be done on the basis of delegated token voting - similar to the default method in the current Governor protocol. For example, the 20 members with most delegated votes are automatically given a councillor role in a DAO.
- The second cannot have delegated voting and should not (in my opinion) work with weighted votes. It works best with N out of M logics that are similar to multisig wallets. For example, at least 5 judges need to vote yes from a total of 9 to revert a previous decision made by councillors.
- Implementing two different voting logics in a single governance regime is currently too complex. A solution is to further abstract away voting systems from the governance system. For example linking an external functions to a particular voting mechanism; and collecting several default voting mechanisms in a separate protocol.
-
A few more specific issues:
- Adding members to roles during a vote can alter quorum and votes needed to pass proposals. Traditionally a similar issue when it comes to ERC20 tokens is handled by the GovernorVotesQuorumFaction extension. I did not implement a similar check yet.
- The use of PUBLIC_ROLE has not been implemented yet.
- OpenZeppelin's
AccessManager
does not fully support multiple roles restricting a single function. If you callgetTargetFunctionRole
you only receive a single role restriction for a particular function. A function that checks if a particular function is restricted by a particular role (as inhasRole
for accounts) does not yet exist.
See the open issues for a full list of proposed features (and known issues).
Contributions make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are greatly appreciated.
If you have a suggestion that would make this better, please fork the repo and create a pull request. You can also simply open an issue with the tag "enhancement". Thank you!
- Fork the Project
- Create your Feature Branch (
git checkout -b feature/AmazingFeature
) - Commit your Changes (
git commit -m 'Add some AmazingFeature'
) - Push to the Branch (
git push origin feature/AmazingFeature
) - Open a Pull Request
Distributed under the MIT License. See LICENSE.txt
for more information.
Seven Cedars - @7__Cedars - cedars7@proton.me
GitHub profile https://github.com/7Cedars
- Patrick Collins
- OpenZeppelin.
- ...