Skip to content

uniswapfoundation/governance-seatbelt

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

411 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

@uniswap/governance-seatbelt

This repository contains tools that make on-chain governance safer, including automated scripts that apply checks to live proposals to allow for better informed voting.

Quick Start

1. Setup

# Install root + frontend dependencies
bun run setup

# Copy and configure environment variables
cp .env.example .env
# Edit .env with your Tenderly credentials and RPC URLs

2. Run a test simulation

# Run a simple simulation to verify setup
SIM_NAME=uni-transfer bun start

Reports are saved to reports/ folder.

2a. Run the opt-in live BNB legacy Wormhole validation

# Load local RPC / explorer env first if your shell has not already done so
set -a; source .env; set +a

# Confirm the legacy BNB Wormhole receiver assumptions still match live chain state
bun run test:wormhole:bnb-legacy:live

This check is intentionally opt-in. It validates the BNB authority ownership, confirms the modern receiver getters still revert, and verifies the legacy sequence storage slot remains readable onchain.

3. View results in the frontend

# Run simulation and start the frontend UI
SIM_NAME=uni-transfer bun run propose

Open http://localhost:3000 to view the simulation results.

4. Create your own simulation

Copy sims/uni-transfer.sim.ts as a template:

cp sims/uni-transfer.sim.ts sims/my-proposal.sim.ts
# Edit my-proposal.sim.ts with your proposal details
SIM_NAME=my-proposal bun run propose

5. Check an existing on-chain proposal

# Check a specific Uniswap proposal by ID
./run-proposal.sh 94

# Or for other DAOs, set environment variables
DAO_NAME=Compound GOVERNOR_ADDRESS=0xc0Da02939E1441F497fd74F78cE7Decb17B66529 bun run check-proposal 43

Reports

Every few hours a GitHub workflow is run which simulates all proposals for each DAO defined in governance-checks.yaml. Reports for each proposal are saved as Markdown files associated with the workflow run. To view the reports, navigate to this repo's Actions, select a workflow, and download the attached artifacts. This will download a zip file containing all reports, where you can find the report you're interested in and open it in your favorite markdown viewer. Soon, alternative viewing options will be available so you don't need to download the files.

If running the simulations locally, you can find the reports in the reports folder.

Some notes on the outputs of reports:

  • If a transaction reverts, that will be reported in the state changes section
  • State changes and events around the proposal execution process, such as the ExecuteTransaction event and queuedTransactions state changes, are omitted from reports to reduce noise
  • Slither analysis for the timelock, governor proxy, and governor implementation is skipped to reduce noise in the output. Note that skipping analysis for the implementation on historical proposals requires an archive node, and a warning will be shown if archive data is required not available
  • ETH balance changes are reported in a dedicated section, showing transfers and net balance changes for each address involved
  • Permission changes (ownership transfers, role grants/revokes, timelock admin changes) are detected and surfaced as warnings, and also emitted as structured data in structuredReport.permissionsDiff

Caching

Seatbelt writes a local cache/ directory to reduce repeated calls to external services (block explorers, Sourcify, etc.). This is used both locally and in CI (the Governance Checks workflow caches cache/ between runs).

  • cache/abis/: ABI JSON fetched from the configured block explorer
  • cache/verification/: contract verification status (Sourcify / block explorer)
  • cache/contract-names/: contract names fetched from the block explorer (used when Tenderly metadata is missing)

Cache refresh behavior:

  • Verification results are re-checked over time (unverified entries expire after ~24h; verified entries after ~30d)
  • Contract-name entries expire after ~30d
  • Stale cache entries are deleted opportunistically when read

Structured Report JSON

When running simulations locally, Seatbelt writes public/simulation-results.json for the frontend. The report.structuredReport object is a stable, machine-readable representation of the report.

For the full JSON schema (including cross-chain preview fields) and consumer guidance, see docs/API.md.

structuredReport.permissionsDiff

If present, this is an array of permission changes detected during the simulation. Each entry has a kind plus additional fields:

  • ownership_transferred: { contractAddress, previous?, next, via }
  • role_granted / role_revoked: { contractAddress, role: { id, name }, account, sender }
  • timelock_admin_changed / timelock_pending_admin_changed: { contractAddress, previous?, next, via }

Proposing via Frontend

This repository also includes a frontend application that allows you to visualize simulation results and create proposals.

Running the Frontend

To run the frontend with simulation results:

  1. Install dependencies (once per clone):

    bun run setup
  2. Provide simulation results (choose one):

    # Option A (recommended): run a real simulation (requires Tenderly + RPCs)
    SIM_NAME=uni-transfer bun run sim
  3. Start the frontend:

    bun run propose
  4. Or do both in one command:

    # Run specific simulation and start frontend
    SIM_NAME=uni-transfer bun run propose

The frontend will be available at http://localhost:3000.

Frontend safety limits (artifact hardening)

The frontend reads frontend/public/simulation-results.json via GET /api/simulation-results. To avoid accidentally wedging the dev server/browser with oversized artifacts, the API enforces a max file size (override with SIMULATION_RESULTS_MAX_BYTES). By default, the API also omits the markdown payload from the response (sets report.markdownReport to ""); pass ?includeMarkdown=1 to include it. If you deploy the frontend publicly, add platform-level rate limiting + error-rate monitoring for /api/*.

Local E2E propose/execute demo (Anvil)

For a quick, deterministic end-to-end demo of the wallet propose → execute wiring (without Tenderly):

bun run e2e:local

This will:

  • start an Anvil chain on http://127.0.0.1:8545 (chain id 31337)
  • deploy a mock governor + target contract
  • write frontend/public/simulation-results.json with a real proposal payload
  • start the frontend dev server configured to use that local chain

In the browser:

  1. Open http://localhost:3000/action
  2. In your wallet, switch to Localhost 31337 (RPC http://127.0.0.1:8545)
  3. Click Propose and confirm the tx
  4. In another terminal: bun run e2e:local:set-proposed
  5. Reload /action, then click Execute

Notes:

  • If port 8545 is already in use, the script will error unless you set E2E_LOCAL_ALLOW_FALLBACK_PORT=1.
  • WalletConnect options in RainbowKit require NEXT_PUBLIC_PROJECT_ID.

Creating Proposals

The frontend allows you to:

  1. View simulation results including state changes, events, and checks the same way reports are visualized
  2. Connect your wallet to sign and submit proposals using the proposal data

Environment setup

You will need to set up a .env.local file in the frontend folder according to the example .env.local

Usage

Adding DAOs to CI

To add a DAO to CI, submit a pull request that adds the desired DAO_NAME and GOVERNOR_ADDRESS to the matrix section of .github/workflows/governance-checks.yaml.

Note that currently only Compound GovernorBravo and OpenZeppelin style governors are supported.

Environment Variable Setup

First, create a file called .env with the following environment variables:

# Etherscan API Key, used when running Slither.
ETHERSCAN_API_KEY=yourEtherscanApiKey

# URL to your Ethereum mainnet RPC (required).
MAINNET_RPC_URL=yourMainnetRpcUrl

# URL to your Arbitrum mainnet RPC (required for cross-chain).
ARBITRUM_RPC_URL=yourArbitrumRpcUrl

# Tenderly access token.
# Access token is obtained from the Tenderly UI via Account > Authorization > Generate Access Token.
TENDERLY_ACCESS_TOKEN=yourAccessToken

# Tenderly user name.
# User name can be found in the URL of your project: https://dashboard.tenderly.co/<userName>/<project_slug>/transactions
# This is `me` for personal accounts.
TENDERLY_USER=userName

# Tenderly project slug.
# Project slug can be found in the URL of your project: https://dashboard.tenderly.co/<userName>/<project_slug>/transactions.
# The name of your tenderly project may not always be your project slug,
# and the project slug can sometimes just be `project`.
TENDERLY_PROJECT_SLUG=projectName

# Define the DAO name and the address of its governor.
DAO_NAME=Uniswap
GOVERNOR_ADDRESS=0x408ED6354d4973f66138C91495F2f2FCbd8724C3

Publishing simulation artifacts (bun upload)

After running a simulation to generate frontend/public/simulation-results.json (see Running Simulations below), use the managed relay by default (no local Vercel setup required):

# Validate artifact only (no publish)
bun upload --validate-only

# Validate + publish via managed relay (default)
bun upload --publish

A successful publish returns relay response URLs (publishId, deploymentUrl, artifactUrl, metadataUrl, and viewerUrl when configured).

For share links, use:

  • <viewerUrl>/p/<publishId> (preferred)
  • <viewerUrl>?artifact=<artifactUrl> (automatic fallback if publish lookup is unavailable)

Notes:

  • artifactUrl points to the published simulation-results.json.
  • deploymentUrl (and its /) is the artifact deployment root and may render a publish landing page, not the canonical frontend viewer.

For complete publish docs (custom artifact paths, relay override, troubleshooting, and fallback guidance), see:

  • docs/PUBLISH_QUICKSTART.md (primary user guide)
  • docs/PUBLISH_CONTRACT.md (publish contract)
  • docs/PUBLISH_RELAY_OPS.md (relay runtime/ops)

Running Simulations

There are two modes of operation:

  1. Run bun start to simulate and run checks on all Governor proposals.
  2. Alternatively, create a file called <analysisName>.sim.ts and run a specific simulation with SIM_NAME=analysisName bun start. See the *.sim.ts files in the sims folder for examples.

When running either of those two modes locally, reports will be saved into a reports/ folder in the root of the repository. The specific path will be ./reports/${daoName}/${governorAddress}/${proposalId}.${extension}. The reports/ folder is gitignored, so when searching for reports in this directory your editor may hide the files by default.

Running Tests

To run the tests:

cd checks
bun test

Or to run a specific test file:

cd checks
bun test tests/check-eth-balance-changes.test.ts

Currently, there is a test for the ETH balance changes check, which verifies that the check correctly identifies and reports ETH transfers and balance changes. As new checks are added or existing checks are modified, corresponding tests should be added to ensure their functionality. The test framework is set up to use Bun's built-in testing capabilities and can be extended to cover additional checks in the future.

Using Seatbelt for Other DAOs

Seatbelt supports any Governor Bravo or OpenZeppelin Governor contract, making it suitable for governance safety analysis across the ecosystem.

Setup for Other DAOs

To use Seatbelt with a different DAO:

  1. Fork this repository to your organization
  2. Update environment variables in your .env file:
    DAO_NAME=YourDAO
    GOVERNOR_ADDRESS=0x... # Your governor contract address
  3. Test the setup with a known proposal:
    bun start

Supported Governor Types

  • Governor Bravo: Used by Compound, Uniswap, and many others
  • OpenZeppelin Governor: Modern governor standard

The system automatically detects the governor type, so no manual configuration is needed.

Example Configurations

Compound:

DAO_NAME=Compound
GOVERNOR_ADDRESS=0xc0Da02939E1441F497fd74F78cE7Decb17B66529

# Check all proposals
bun start

# Check specific proposal
bun run check-proposal 43

Custom DAO:

DAO_NAME=YourDAO
GOVERNOR_ADDRESS=0x... # Your governor address
bun start

Custom Simulations

For hypothetical or new proposals, create a simulation file in sims/:

// sims/my-proposal.sim.ts
export const config = {
  type: 'proposed', // or 'executed'
  daoName: 'Compound',
  governorAddress: '0xc0Da02939E1441F497fd74F78cE7Decb17B66529',
  governorType: 'bravo',
  // ... proposal details
};

Then run: SIM_NAME=my-proposal bun start

Testing Setup

To verify your setup works:

# Test with existing simulation
SIM_NAME=compound-43 bun start

Security

Pre-commit Hooks

This repository uses Husky to run secretlint before each commit, preventing accidental secret leaks. Hooks install automatically when you run bun install.

Reporting Vulnerabilities

See SECURITY.md for our security policy and vulnerability reporting process.

About

Make governance safer

Resources

License

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages

  • TypeScript 99.7%
  • Other 0.3%