Seatbelt for Governance: Supporting OpenZeppelin Governors
December 19, 2022 / Matt Solomon
ScopeLift is excited to announce that Seatbelt now supports OpenZeppelin Governors. This comes in addition to Governor Bravo, which has long been supported.
Seatbelt is a collaboration between Uniswap and ScopeLift to make governance safer by simulating proposals and generating human-readable reports explaining them. This expansion allows for even more DAOs to use Seatbelt to strengthen their governance process and ensure the security of their proposals.
About Seatbelt
Governance proposals are carefully crafted transactions. Each proposal is unique, and there's typically no audit process involved. The inherent complexity of Ethereum can make it difficult for users to understand what a proposal does, leaving them to trust the proposal author. Mistakes in the proposal itself can result in proposals that revert when executed, lose DAO funds, or even leave governance permanently unusable.
These kinds of mistakes can happen, even to the most experienced proposal authors. Recently severalproposals were submitted to Compound DAO that would have reverted due to a minor error. All of these cases were caught by Seatbelt.
The Seatbelt report for Compound Proposal 95 showing the error in the proposal which would have caused it to revert.
Seatbelt works by simulating proposals before they actually execute. Every few hours Seatbelt simulates on-chain proposals for multiple DAOs, runs a series of checks against the simulation result, and generates reports for voters to review. These reports include:
- A list of all state changes.
- A list of all events emitted.
- All address used in the proposal.
- Verification that contracts are verified on Etherscan.
- The data being submitted in human readable form (i.e. decoded calldata).
- Warnings from the Solidity compiler for all contracts.
- Automated code analysis using Trail of Bits' Slither.
Using Seatbelt for your DAO
Today, automated Seatbelt checks run for the Uniswap, Compound, ENS, Unlock, and Idle Finance DAOs. The resulting reports can be found in the "Actions tab of the Seatbelt repository. If you'd like to add another supported DAO to these checks, follow the steps in the "Adding DAOs to CI" section of the Seatbelt repository's README.
Seatbelt also lets you simulate proposals locally before sending them on-chain, so you can generate a report locally to verify a proposal does what you expect before putting it up for a vote. The "Running Simulations" portion of the README explains how to do this, and you can find a sample local simulation file here.
We are committed to improving DAO governance and are grateful for the grant from the Uniswap Grants Program to develop Seatbelt. By supporting a wider range of governance contracts, we hope to make Seatbelt an even more valuable tool for DAOs. If you're interested in contributing to Seatbelt, please reach out. And if you're the kind of person who enjoys the technical nitty gritty, read on to learn how we use Tenderly to simulate governance proposals.
Addendum: Technical Details of Governance Proposal Simulation
Simulating transactions can be difficult. If a governance proposal is active, it can't yet be executed. That's because votes are ongoing, and proposal has not yet passed. Simulating the transaction naively will therefore revert. We leverage the Tenderly Simulation API to workaround these limitations.
We modify the chain state in the simulation environment to pretend we're at a point in time where the proposal can be executed. What exactly must be modified depends on whether we're simulating a Bravo or OpenZeppelin Governor, so first we look at the contract's ABI to infer whether we're simulating a Bravo or OpenZeppelin Governor.
Each Governor has many differences such as storage layouts, voting tokens, and quorum thresholds. Fortunately, variable names are consistent—there's one set of names for Bravo and another for OpenZeppelin—so we leverage this, along with Tenderly's API, to make the state changes simple.
First we compose an object describing all state overrides we want for a given address, based on variable names. For example, some overrides for an OpenZeppelin Governor are:
governorStateOverrides = {
_proposalVotes[id].forVotes = votingTokenSupply
_proposalVotes[id].againstVotes = '0'
_proposalVotes[id].abstainVotes = '0'
}
This means we fake a proposal succeeding and ensure it reached quorum by saying "every single token holder voted in favor, and no one voted against or abstained". We send this to Tenderly's /contracts/encode-states
endpoint, and using the contract's source code it returns the actual slot numbers and data for each variable we specified overriding.
We take that result, configure a few other properties like block number and timestamp, and send the payload to Tenderly's /simulate
endpoint. This simulates the transaction, using the overrides we requested to force the proposal to be valid. The response is a large JSON object with every part of the simulated transaction already decoded.
That JSON response is then used an input to various "check" methods which parse the JSON and run slither against the source code. Each check method outputs it's own markdown report, which is merged together to generate the full markdown report. That markdown is then parsed and converted into HTML and PDF formats, all of which are included as workflow artifacts for each CI run.