Using Governance Contracts
This page documents the various governance contracts and their tests that can be found in the governance examples repository.
GovernorTimelockCompound
GovernorTimelockCompound.sol
is an abstract smart contract that is provided by the Openzeppelin project, and it belongs to the Governance category of contracts. In general, governance contracts are used to control on chain-governance (i.e. decision-making). GovernorTimelockCompound.sol
is also a Governor.sol
contract which means that it inherits all the logic and primitives for on chain governance. In addition to that GovernorTimelockCompound.sol
also binds the execution of a proposal to a Compound Timelock which means that any proposal will have a time delay enforced by the TimelockController.sol
contract. Since GovernorTimelockCompound.sol
is abstract, to use it one would have to create a new so-called mock contract that inherits from it and implement the necessary functionality for their use-case.
Example Use-case
A common use-case for on-chain governance contracts is voting for a proposal. This could be used in DAOs where stakeholders have the ability to vote on a proposal that will execute if a threshold of participants agrees with its execution. A proposal is just a sequence of actions, and it consists of target addresses, calldata which encodes a function call, and an amount of VET to include. Essentially it is a collection of transactions that can either transfer value or call a function in any contract. In order to create a proposal an Admin(?) of the Governor contract can call the propose
function and pass the target addresses, value and data as well as a proposal description. Then the stakeholders of the DAO can cast a vote using the castVote
function. In our case GovernorTimelockCompound.sol
will delegate the proposal activation to TimelockCompound.sol
which will impose a time delay between proposal activation, and it's actually taking effect, giving time to users to react to the change before it takes effect.
Changes
In order to make some tests provided by Openzeppelin work with the various governance contracts some changes had to be made. For example a new function was added to CompTimelock.sol where one can change the admin of the contract externally after its construction. In the corresponding test in GovernorTimelockCompound.test.js the admin value of the timelock contract is set to the address of the Governor
instance that is also deployed in the same test case. Originally in the test there is a function that can predicts the Governor's contract address and passes it as a value in the Timelock contract's constructor before the Governor is deployed. But since there are differences between Ethereum and VeChain, the same contract prediction does not work for VeChain contracts. Thus, why we had to manually set the admin value.
How to use
In order to use GovernorTimelockCompound.sol
for your own project the following steps are required:
Import the required contracts
Create a mock contract
Create a new contract that inherits from GovernorTimelockCompound.sol
in order to inherit its functionality.
While the core logic is given by the Governor contract we also need to inherrit some other helper contracts to control different parameters. For example, we will also need to inherit the GovernorSettings.sol
in order to manage some of the governance settings such as voting delay, voting period duration and proposal threshold, GovernorVotesQuorumFraction.sol
in order to determine how many votes are needed for quorum, GovernorCountingSimple.sol
in order to provide users with options in regard to voting (e.g. For, Against and Abstain) and of course GovernorTimelockCompound.sol
for the main functionality which is connecting the governor contract with an instance of a compound timelock contract. Taking everything into consideration the updated constructor looks like this:
Notice that we also need to call the Governor
and GovernorVotes
constructors even though we have not directly inherited from these contracts. The reason is that other contracts that we are using inherit from them. For example GovernorVotesQuorumFraction.sol
inherits from GovernorVotes.sol
thus we need to also call its constructor if we want to customize the functionality. Finally, note that in order to be able to deploy the mock contract we also need to have a deployed version of CompoundTimelock
since its address is required as an input parameter to the GovernorTimelockCompound
constructor.
Implement Necessary Functions
After we have inherited from GovernorTimelockCompound.sol
and helper contracts we need to override some of its functions because they are implemented by multiple contracts. This creates a problem because there are two or more base classes that define a function with the same name and parameter types, and our derived contract is trying to inherit from them. This creates an ambiguity for the derived contract, as it is not clear which version of the function to use. The derived contract in this case is of course our GovernorTimelockCompoundMock.sol
contract. We will use the override keyword in Solidity to define the concrete functionality in order to remove ambiguity and be able to compile our contracts and/or define new functionality. The following functions will be overridden:
supportsInterface
returns a boolean value representing whether the contract supports a given interfacequorum
returns the number of votes a proposal hasstate
returns the state of a proposalproposalThreshold
returns the number of votes required in order for a voter to become a proposer_execute
executes a proposal_cancel
cancels proposal_executor
returns the address of the proposal's executor
The complete mock contract can be found below
Note that when a contract inherits from multiple other contracts that implement the same virtual function (e.g. A
), and if that contract overrides the function by running super.A()
then the function that runs will be the one that is last in the inheritance list. For example in the following example
when running super.foo()
in the Test contract it will execute the implementation found in Parent1 since its last in the inheritance list.
Testing
In order to test the functionality and demonstrate potential use cases, one can create test cases where an owner of the mock contract creates a proposal and a number of voters vote on it. Depending on the votes the proposal can be executed at a future time or rejected. This is demonstrated in the tests included in the examples repository. To run the tests provided in the examples repository simply run:
GovernorTimelockControl
GovernorTimelockControl.sol
is another abstract contract provided by the Openzeppelin project. It is very similar to the aforementioned GovernorTimelockCompound.sol
contract in that its main purpose is to be used for on-chain governance. The main difference is the choice of time-lock between the two contracts. While GovernorTimelockCompound.sol
uses the TimelockCompound.sol
contract for time-lock purposes GovernorTimelockControl.sol
uses a TimelockController instance.
How to use
Import the required contracts
Implement necessary functions
Similarly to GovernorTimelockCompound.sol
we need to implement the same functions in order to create the mock contract. The full contract can be found below:
Testing
Finally, in order to test our newly created mock contract we need to run it with the following command
Last updated