An upgrade to a "Bravo" compatible Governor for the PoolTogether DAO, built using the OpenZeppelin implemented and Flexible Voting.
Clone the repo
git clone [email protected]:ScopeLift/pooltogether-governor-upgrade.git
cd pooltogether-governor-upgrade
Copy the .env.template
file and populate it with values
cp .env.template .env
# Open the .env file and add your values
forge install
forge build
forge test
Formatting is done via scopelint. To install scopelint, run:
cargo install scopelint
scopelint fmt
scopelint check
Some tests will not show up when running scopelint spec
because the methods they are testing are inherited in the PoolTogetherGovernor
. In order to get an accurate picture of the tests with scopelint spec
add an explicit propose
method to the PoolTogetherGovernor
. It should look like this:
function propose(
address[] memory targets,
uint256[] memory values,
bytes[] memory calldatas,
string memory description
) public override(Governor, IGovernor) returns (uint256) {
return Governor.propose(targets, values, calldatas, description);
}
script/Deploy.s.sol
- Deploys the PoolTogetherGovernor contract
To test these scripts locally, start a local fork with anvil:
anvil --fork-url YOUR_RPC_URL --fork-block-number 17665572
Then execute the deploy script.
NOTE: You must populate the DEPLOYER_PRIVATE_KEY
in your .env
file for this to work.
forge script script/Deploy.s.sol --tc Deploy --rpc-url http://localhost:8545 --broadcast
Pull the contract address for the new Governor from the deploy script address, then execute the Proposal script.
NOTE: You must populate the PROPOSER_PRIVATE_KEY
in your .env
file for this to work. Additionally, the
private key must correspond to the proposer
address defined in the Proposal.s.sol
script. You can update this
variable to an address you control, however the proposal itself will still revert in this case, unless you provide
the private key of an address that has sufficient POOL Token delegation to have the right to submit a proposal.
forge script script/Propose.s.sol --sig "run(address)" NEW_GOVERNOR_ADDRESS --rpc-url http://localhost:8545 --broadcast
When upgrading PoolTogether's Alpha Governor implementation we needed to fallback on non-standard interfaces.
PoolTogether's timelock is not compatible with Openzeppelin's ICompoundTimelock
which is used by GovernorTimelockCompound
, and is in charge of queuing and executing proposals. Due to this incompatibility we had to fork the GovernorTimelockCompound.sol
and change the interface to conform to the PoolTogether interface.
The main issue with the interface is that PoolTogether's contract has a method gracePeriod
when ICompoundTimelock
is expecting the method to be called GRACE_PERIOD
. This function is used in GovernorTimelockCompound
's state
method and caused us to have to fork the GovernorTimelockCompound
. Below is a table of all the changes we had to make between the Openzeppelin GovernorTimelockCompound
and our forked version.
Change name | original | Changed version |
---|---|---|
_timelock type | here | here |
Constructor argument type | here | here |
state grace period call | here | here |
Cast timelock to address | here | here |
Update updateTimelock function args | here | here |
Update _updateTimelock function args | here | here |
A full diff of the two contracts can be found here
In the new Governor we inherit from GovernorVotesComp
which expects an ERC20VotesComp
token. The PoolTogether token is missing a few functions that exist in ERC20VotesComp
and we have listed those missing functions below. The reason we did not fork the Openzeppelin contract and use a custom interface was because GovernorVotesComp
calls a single function getPriorVotes
which exists on the PoolTogether token.
A full diff of the two contracts can be found here
function DOMAIN_SEPARATOR() external view returns (bytes32);
function decreaseAllowance(address spender, uint256 subtractedValue) external returns (bool);
function getPastTotalSupply(uint256 blockNumber) external view returns (uint256);
function getPastVotes(address account, uint256 blockNumber) external view returns (uint256);
function getVotes(address account) external view returns (uint256);
function increaseAllowance(address spender, uint256 addedValue) external returns (bool);
function numCheckpoints(address account) external view returns (uint32);
function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) external;
- checkpoints
- ERC20Votes:
function checkpoints(address account, uint32 pos) external view returns (Checkpoint memory);
- POOL:
function checkpoints(address, uint32) external view returns (uint32 fromBlock, uint96 votes);
- ERC20Votes:
This repo heavily leverages fuzz fork tests causing a significant number of RPC requests to be made. We leverage caching to minimize the number of RPC calls after the tests are run for the first time, but running these tests for the first may cause timeouts and consume a significant number of RPC calls.