Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .env.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# RPC endpoint for an Ethereum mainnet archive node, used by the mainnet fork tests and for
# running the deploy and proposal scripts. Copy this file to `.env` and fill in the value; `.env`
# is gitignored and the URL (which typically embeds an API key) must be kept secret.
MAINNET_RPC_URL=
89 changes: 46 additions & 43 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ jobs:

test:
runs-on: ubuntu-latest
env:
# The mainnet fork tests read this RPC URL via the `mainnet` alias in foundry.toml. Set the
# MAINNET_RPC_URL secret in the repository settings.
MAINNET_RPC_URL: ${{ secrets.MAINNET_RPC_URL }}
steps:
- uses: actions/checkout@v3

Expand All @@ -35,51 +39,50 @@ jobs:
- name: Run tests
run: forge test

# TODO: turn back on when the tests are implemented
# coverage:
# runs-on: ubuntu-latest
# env:
# FOUNDRY_PROFILE: coverage
# steps:
# - uses: actions/checkout@v3
coverage:
runs-on: ubuntu-latest
env:
FOUNDRY_PROFILE: coverage
MAINNET_RPC_URL: ${{ secrets.MAINNET_RPC_URL }}
steps:
- uses: actions/checkout@v3

# - name: Install Foundry
# uses: foundry-rs/foundry-toolchain@v1

# - name: Run coverage
# run: forge coverage --report summary --report lcov

# # To ignore coverage for certain directories modify the paths in this step as needed. The
# # below default ignores coverage results for the test and script directories. Alternatively,
# # to include coverage in all directories, comment out this step. Note that because this
# # filtering applies to the lcov file, the summary table generated in the previous step will
# # still include all files and directories.
# # The `--rc lcov_branch_coverage=1` part keeps branch info in the filtered report, since lcov
# # defaults to removing branch info.
# - name: Filter directories
# run: |
# sudo apt update && sudo apt install -y lcov
# lcov --remove lcov.info 'test/*' 'script/*' --output-file lcov.info --rc lcov_branch_coverage=1

# # This step posts a detailed coverage report as a comment and deletes previous comments on
# # each push. The below step is used to fail coverage if the specified coverage threshold is
# # not met. The below step can post a comment (when it's `github-token` is specified) but it's
# # not as useful, and this action cannot fail CI based on a minimum coverage threshold, which
# # is why we use both in this way.
# - name: Post coverage report
# if: github.event_name == 'pull_request' # This action fails when ran outside of a pull request.
# uses: romeovs/lcov-reporter-action@v0.3.1
# with:
# delete-old-comments: true
# lcov-file: ./lcov.info
# github-token: ${{ secrets.GITHUB_TOKEN }} # Adds a coverage summary comment to the PR.
- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1

# - name: Verify minimum coverage
# uses: zgosalvez/github-actions-report-lcov@v2
# with:
# coverage-files: ./lcov.info
# # TODO: bump this back up once tests are implemented.
# minimum-coverage: 0 # Set coverage threshold.
- name: Run coverage
run: forge coverage --report summary --report lcov

# To ignore coverage for certain directories modify the paths in this step as needed. The
# below default ignores coverage results for the test and script directories. Alternatively,
# to include coverage in all directories, comment out this step. Note that because this
# filtering applies to the lcov file, the summary table generated in the previous step will
# still include all files and directories.
# The `--rc lcov_branch_coverage=1` part keeps branch info in the filtered report, since lcov
# defaults to removing branch info.
- name: Filter directories
run: |
sudo apt update && sudo apt install -y lcov
lcov --remove lcov.info 'test/*' 'script/*' --output-file lcov.info --rc lcov_branch_coverage=1

# This step posts a detailed coverage report as a comment and deletes previous comments on
# each push. The below step is used to fail coverage if the specified coverage threshold is
# not met. The below step can post a comment (when it's `github-token` is specified) but it's
# not as useful, and this action cannot fail CI based on a minimum coverage threshold, which
# is why we use both in this way.
- name: Post coverage report
if: github.event_name == 'pull_request' # This action fails when ran outside of a pull request.
uses: romeovs/lcov-reporter-action@v0.3.1
with:
delete-old-comments: true
lcov-file: ./lcov.info
github-token: ${{ secrets.GITHUB_TOKEN }} # Adds a coverage summary comment to the PR.

- name: Verify minimum coverage
uses: zgosalvez/github-actions-report-lcov@v2
with:
coverage-files: ./lcov.info
minimum-coverage: 100 # Set coverage threshold.

lint:
runs-on: ubuntu-latest
Expand Down
20 changes: 16 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,9 @@ Profiles (see `foundry.toml`):

Keep the **default** and **ci** solc settings in sync — they are the production build settings; never
change solc config for `ci` alone. Use `scopelint`, not bare `forge fmt`; it's a superset and is what
CI enforces. Mainnet fork tests will need an `ETH_RPC_URL` (e.g. via `--fork-url`).
CI enforces. The mainnet fork tests read the `mainnet` RPC alias from `foundry.toml`, backed by
`MAINNET_RPC_URL` in `.env` (copy `.env.template`; the URL embeds an API key and must stay
secret). CI supplies it via the `MAINNET_RPC_URL` repository secret.

## Conventions

Expand Down Expand Up @@ -217,9 +219,19 @@ CI enforces. Mainnet fork tests will need an `ETH_RPC_URL` (e.g. via `--fork-url
**upgrade proposal script** (`ProposeGovernorUpgrade[Mainnet].s.sol`) are in place. Both carry
`TODO`s to confirm with stakeholders before running (Governor name, vote extension, proposal
guardian; new Governor address, proposer, proposal text).
- **No tests yet.** No Franchiser code yet.
- Up next: the mainnet-fork test suite (see [Deliverables](#deliverables) and
[Testing strategy](#testing-strategy)), then the Franchiser workstream.
- The **mainnet fork integration suite** (`test/*.integration.t.sol`) is in place: it deploys the
new Governor with the real deploy script, submits the upgrade proposal with the real proposal
script, and exercises the upgrade lifecycle, post-upgrade governance, quorum behavior
(settable + late-quorum), and the Proposal Guardian. Shared helpers live in `test/helpers/`;
each suite has a `…MainnetScript` provenance concrete, with room for a `…MainnetDeployed`
concrete after the real deployment. Proposals are voted through by an electorate of **real
delegates** whose live weights are read from the fork in `setUp`. The suite pins `FORK_BLOCK`
in `test/helpers/GitcoinGovernorUpgradeTestBase.sol` — when bumping it, re-verify the
`PROPOSER` delegate still clears the proposal threshold and the electorate still clears quorum
(`setUp` asserts both weights loudly, and quorum-boundary tests assert their own weight
preconditions).
- No Franchiser code yet.
- Up next: the Franchiser workstream (see [Deliverables](#deliverables)).
- CI runs `forge build`, `forge test`, and `scopelint check`. Coverage and Slither jobs are scaffolded
but commented out in `.github/workflows/ci.yml`.

Expand Down
40 changes: 34 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ forge test # run the test suite
FOUNDRY_PROFILE=lite forge test # faster local iteration (optimizer off)
```

The tests fork Ethereum mainnet, so they need an archive-node RPC endpoint: copy `.env.template`
to `.env` and set `MAINNET_RPC_URL`. The URL typically embeds an API key — keep it secret (`.env`
is gitignored). CI supplies it through the `MAINNET_RPC_URL` repository secret.

Formatting and linting use [scopelint](https://github.com/ScopeLift/scopelint), a superset of
`forge fmt`:

Expand All @@ -39,8 +43,8 @@ scopelint check # verify formatting and conventions (also run in CI)

- `script/` — deployment and governance-proposal scripts (see [Scripts](#scripts)). The Governor
deploy and upgrade-proposal scripts are in place; Franchiser scripts are still being built.

The test suite (`test/`) is still being built.
- `test/` — mainnet fork integration tests simulating the full upgrade and exercising the upgraded
Governor (see [Testing](#testing)).

## Scripts

Expand All @@ -58,14 +62,14 @@ before broadcasting:

```sh
forge script script/DeployGitcoinGovernorWithGuardianMainnet.s.sol:DeployGitcoinGovernorWithGuardianMainnet \
--rpc-url "$ETH_RPC_URL"
--rpc-url "$MAINNET_RPC_URL"
```

Then broadcast and verify (using an encrypted keystore account set up with `cast wallet import`):

```sh
forge script script/DeployGitcoinGovernorWithGuardianMainnet.s.sol:DeployGitcoinGovernorWithGuardianMainnet \
--rpc-url "$ETH_RPC_URL" \
--rpc-url "$MAINNET_RPC_URL" \
--account deployer \
--broadcast \
--verify
Expand All @@ -89,22 +93,46 @@ Dry-run first to simulate the proposal and review the transaction it would send:

```sh
forge script script/ProposeGovernorUpgradeMainnet.s.sol:ProposeGovernorUpgradeMainnet \
--rpc-url "$ETH_RPC_URL"
--rpc-url "$MAINNET_RPC_URL"
```

Then broadcast as the proposer (using an encrypted keystore account set up with
`cast wallet import`):

```sh
forge script script/ProposeGovernorUpgradeMainnet.s.sol:ProposeGovernorUpgradeMainnet \
--rpc-url "$ETH_RPC_URL" \
--rpc-url "$MAINNET_RPC_URL" \
--account proposer \
--broadcast
```

> 🚧 **Still under development.** The Franchiser scripts — deployment and delegation — are not yet
> available. Usage instructions will be documented here as they land.

## Testing

The integration tests (`test/*.integration.t.sol`) run against a fork of Ethereum mainnet pinned
to a fixed block, and simulate the entire upgrade the way it will actually happen: the real deploy
script deploys the new Governor onto the fork, the real proposal script submits the upgrade
proposal to the currently active Governor, and delegates vote it through to execution — after
which the suites exercise the upgraded Governor in place:

- `GovernorUpgradeProposal` — the upgrade proposal's lifecycle on the active Governor: passing it
hands the Timelock to the new Governor; defeating it leaves the current Governor in control.
- `PostUpgradeGovernance` — day-to-day governance after adoption: proposals moving ETH and tokens
held by the Timelock, updating the Governor's own settings, fractional voting, and Timelock
expiry.
- `PostUpgradeQuorumBehavior` — the DAO adjusting its own quorum, and the late-quorum protection
extending voting when quorum is reached near the deadline.
- `PostUpgradeProposalGuardian` — the Proposal Guardian cancelling proposals at every cancelable
lifecycle stage, the limits of that power, and the DAO replacing the guardian.

Each suite is written against an abstract base that leaves *how the system comes into being* to a
small concrete contract at the bottom of the file. Today each file has a `…MainnetScript` concrete
that deploys via the real deploy script; once the new Governor is live on mainnet, a
`…MainnetDeployed` concrete pointing at the deployed address can rerun the same suites as a
post-deployment acceptance check.

## License

This project is licensed under the [GNU Affero General Public License v3.0](./LICENSE), with the
Expand Down
3 changes: 3 additions & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
# Speed up compilation and tests during development.
optimizer = false

[rpc_endpoints]
mainnet = "${MAINNET_RPC_URL}"

[fmt]
bracket_spacing = false
int_types = "long"
Expand Down
Loading
Loading