Skip to content
Open
8 changes: 4 additions & 4 deletions .github/ISSUE_TEMPLATE/01_bug_report.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,19 @@ body:
value: |
Thanks for taking the time to fill out this bug report!

If you’re unsure whether the issue you’re having is a bug, you can post in [Discussions](https://github.com/OpenZeppelin/midnight-contracts/discussions) instead.
If you’re unsure whether the issue you’re having is a bug, you can post in [Discussions](https://github.com/OpenZeppelin/compact-contracts/discussions) instead.

- type: checkboxes
id: prerequisites
attributes:
label: Prerequisites
description: Please ensure you have completed all of the following.
options:
- label: I have read the [Contributing Guidelines](https://github.com/OpenZeppelin/midnight-contracts/CONTRIBUTING.md#creating-an-issue).
- label: I have read the [Contributing Guidelines](https://github.com/OpenZeppelin/compact-contracts/blob/main/CONTRIBUTING.md#opening-an-issue).
required: true
- label: I agree to follow the [Code of Conduct](https://github.com/OpenZeppelin/midnight-contracts/CODE_OF_CONDUCT.md).
- label: I agree to follow the [Code of Conduct](https://github.com/OpenZeppelin/compact-contracts/blob/main/CODE_OF_CONDUCT.md).
required: true
- label: I have searched for [existing issues](https://github.com/OpenZeppelin/midnight-contracts/issues) that already report this problem, without success.
- label: I have searched for [existing issues](https://github.com/OpenZeppelin/compact-contracts/issues) that already report this problem, without success.
required: true

- type: textarea
Expand Down
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: OpenZeppelin Midnight Contracts Community Support
url: https://github.com/OpenZeppelin/midnight-contracts/discussions
url: https://github.com/OpenZeppelin/compact-contracts/discussions
about: Please ask and answer questions here.
2 changes: 1 addition & 1 deletion .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Fixes #??? <!-- Fill in with issue number -->
<!-- Feel free to submit a PR or Draft PR even if some items are pending. -->
<!-- Some of the items may not apply. -->

- [ ] I have read the [Contributing Guide](../CONTRIBUTING.md#your-first-code-contribution)
- [ ] I have read the [Contributing Guide](../CONTRIBUTING.md#a-typical-workflow)
- [ ] I have added tests that prove my fix is effective or that my feature works. See [GUIDELINES.md#testing](../GUIDELINES.md#testing) for more information.
- [ ] I have added documentation for new methods or changes to existing method behavior. See [GUIDELINES.md#documentation](../GUIDELINES.md#documentation) for more information.
- [ ] CI Workflows Are Passing
Expand Down
551 changes: 165 additions & 386 deletions CONTRIBUTING.md

Large diffs are not rendered by default.

10 changes: 4 additions & 6 deletions GUIDELINES.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,14 +60,12 @@ Some other examples of automation are:

## Pull requests

Pull requests are squash-merged to keep the `main` branch history clean. The title of the pull request becomes the commit message, so it should be written in a consistent format:
Pull requests are squash-merged to keep the `main` branch history clean. The title of the pull request becomes the commit message, so it must follow [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/): `type(scope): summary`, e.g. `fix(token): reject zero-amount transfers`.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 blocking: this PR's own title (Refactor contributing.md) does not follow the rule it adds, and the squash commit takes the PR title. Retitle to e.g. docs: refactor CONTRIBUTING.md and split out TESTING.md.

added by claude (dev3-code-review skill)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

edited the title


1) Begin with a capital letter.
1) Keep the summary short and imperative: "add feature X" and not "adds feature X" or "added feature X".
2) Do not end with a period.
3) Write in the imperative: "Add feature X" and not "Adds feature X" or "Added feature X".

This repository does not follow conventional commits, so do not prefix the title with "fix:" or "feat:".
3) Use the scope to name the module or area the change touches.

Work in progress pull requests should be submitted as Drafts and should not be prefixed with "WIP:".

Branch names don't matter, and commit messages within a pull request mostly don't matter either, although they can help the review process.
Branch names don't matter. Commits within a pull request must be signed and follow the same Conventional Commits format; see [`CONTRIBUTING.md`](CONTRIBUTING.md#commit-and-pr-conventions).
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,8 @@ yarn compile
yarn test
```

Integration and live suites are described in [TESTING.md](TESTING.md).

### Check/apply Biome formatter

```bash
Expand Down
2 changes: 1 addition & 1 deletion SUPPORT.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
If you're looking for support for this library, check out:

* Developer Documentation &mdash; [Compact Contracts API Documentation](https://docs.openzeppelin.com/contracts-compact/)
* Github Discussion Board &mdash; [Github Discussions](https://github.com/OpenZeppelin/midnight-contracts/discussions)
* Github Discussion Board &mdash; [Github Discussions](https://github.com/OpenZeppelin/compact-contracts/discussions)
* Midnight Forum &mdash; [Midnight Forum Technical Questions](https://forum.midnight.network/t/about-the-technical-questions-category/11)

On the Midnight Forum, there are a bunch of helpful community members that should be willing to point you in the right direction.
153 changes: 153 additions & 0 deletions TESTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
# Testing

How to run the test suites locally and how the live suite runs in CI. What every change must
be tested with lives in [`GUIDELINES.md`](GUIDELINES.md#testing); the coverage gate lives in
[`CONTRIBUTING.md`](CONTRIBUTING.md#code-quality-standards).

Run all commands from the repository root. Enable Corepack once (`corepack enable`) so `yarn`
resolves to the version pinned in `package.json`.

## Unit tests

Unit tests run against an in-process mock backend: no network, ZK proving skipped.

```bash
yarn test
yarn test:coverage # run locally (not yet a CI check)
```

## Integration tests

Composed-contract specs under `contracts/test/integration/specs`: several modules assembled
into one contract under `test/integration/_mocks`, then deployed and driven as a unit. Same
mock backend as the unit tests.

```bash
yarn test:integration
```

## Live tests

Live tests run against a local Midnight network (node, indexer and proof server) defined in
[`local-env.yml`](local-env.yml). They need [Docker](https://docs.docker.com/get-docker/) and a
completed `yarn install`. The runner builds the target's contracts with real ZK keys, which is
slow; do not interleave `SKIP_ZK=true` builds with live runs, since a key-less build forces a
full regeneration.

```bash
yarn test:live # every live category, sequentially
yarn test:live multisig # one category
yarn test:live multisig ShieldedTreasury # files within a category
yarn test:live integration # the composed-contract specs (or: yarn test:integration:live)
yarn test:live --list # the live targets, as the CI matrix reads them
```

A run compiles the target's slice, starts the stack (`make env-up`, itself a reset), runs a
harness smoke, then the specs, each category on a freshly reset node. The runner stops the
stack on every exit path, Ctrl-C included; no manual `env:up` or `env:down` is needed.
`MIDNIGHT_LIVE_KEEP_ENV=1` leaves it running for inspection. Container logs land in `logs/`
either way.

### Targets and filters

Every `contracts/src/<category>` with tests is a live category; today that is `access`,
`crypto`, `multisig`, `security`, `token` and `utils`. A category that must not run live is
an explicit opt-out in `EXCLUDED_CATEGORIES` in [`scripts/live/targets.ts`](scripts/live/targets.ts)
(only the legacy `archive` today).

`integration` is a target of its own, not a category: an unscoped run skips it, so ask for it
by name. Only one target runs per invocation, since both live projects draw wallets from the
same genesis-funded pool. The integration live block deploys the composed contract and reads
its supply back; the functional integration specs stay dry-only because they mutate private
state mid-test, which the live backend rejects.

The first argument names the target. Any further argument is a filename filter, matched the
way vitest matches it (a case-insensitive substring of the path) against that target's own
spec files, so a name that prefixes others runs all of them. In an unscoped run, a target the
filter matches nothing under is skipped; a filter that matches nothing anywhere aborts.

### Flake check and exit codes

If any files fail, a second round re-runs just those files on a fresh node with one worker:

- Fails round 1, passes round 2: **FLAKY** (exit 0, reported loudly).
- Fails both rounds, or never reports in round 2: **REAL** (exit 1).
- Infrastructure abort (stack, compile, lock): exit 2.

The same semantics apply to a scoped run, so a green result means the same thing as for the
full suite.

### Fresh node rules

All live tests share one node, so state left by an earlier run can fail a later one. A guard
enforces two rules and fails fast, before any wallet build:

1. **Start from a fresh node.** Stale state makes shielded spends fail with node
`Custom error: 103`. The guard aborts if it finds any shielded coin event beyond genesis.
The runner resets for you; reset manually with `yarn env:up`.
2. **One live run at a time.** A pid-stamped lock (`contracts/logs/.live-run.lock`) makes a
second concurrent run abort.

| Variable | Default | Effect |
| --- | --- | --- |
| `MIDNIGHT_LIVE_WORKERS` | 3 | Parallel spec files under `unit-live` (max 3, one genesis-funded deployer each). |
| `MIDNIGHT_LIVE_ALLOW_DIRTY` | unset | `1` skips the freshness check. |
| `MIDNIGHT_LIVE_MAX_COIN_EVENTS` | 0 | Coin events beyond genesis tolerated before "not fresh". |
| `MIDNIGHT_LIVE_MAX_SCAN_BLOCKS` | 3600 | Above this indexer head, the guard asks you to `env:up` rather than scan. |
| `MIDNIGHT_LIVE_KEEP_ENV` | unset | `1` leaves the stack running after the run. |

### Reading the output

`unit-live` runs up to 3 workers in parallel, so output interleaves. It is tagged per worker:
a `▶ live worker N/3 ready` banner when a worker's wallets are funded, a `[wN] ❯ <file>` line
as each spec file starts, and a `[wN] ✓ <test> (<ms>) [done/total]` line per test. Each worker
also writes `logs/live-harness-wN.log`. `integration-live` runs one worker.

To keep a readable log, force color and pipe to `tee`: piping makes vitest print one line per
result instead of a spinner, and `FORCE_COLOR=1` keeps the color.

```bash
FORCE_COLOR=1 yarn test:live multisig 2>&1 | tee logs/live-multisig.ansi
```

Render the `.ansi` file rather than reading it raw: `less -R` in a terminal, or an ANSI
extension such as [`iliazeus.vscode-ansi`](https://marketplace.visualstudio.com/items?itemName=iliazeus.vscode-ansi)
in VS Code. On Linux, prefix `systemd-inhibit --why="live tests"` for a long run.

### Live tests in CI

The live suite takes hours, so [`live.yml`](.github/workflows/live.yml) runs it separately
from the PR checks and is never a required check:

- **Nightly** on `main`. A failed nightly opens (or comments on) a `live-nightly` tracking
issue, which closes automatically on the next green run.
- **On demand**: run `Live Test Suite` from the Actions tab (or `gh workflow run live.yml`),
optionally scoped with the `target` and `filter` inputs.
- **On a PR**: apply a `live-tests:<target>` label (e.g. `live-tests:multisig`), or
`live-tests:all` for every target. Re-apply the label for a fresh run after new pushes. The
bare `live-tests` label is rejected by the plan job.

The run is one pipeline per target. A plan job asks the runner which targets exist and which
spec files each holds. Each target gets a compile job that builds its slice once with real ZK
keys, feeding a matrix with one job per spec file that runs its file on its own runner and
stack. A file whose estimated duration exceeds the leg budget (30 tests at ~55 s each, or the
previous run's measured timings when available) is split by its describe blocks into several
legs via [`scripts/ci/split.ts`](scripts/ci/split.ts). `KNOWN_TARGETS` in `live.yml` must
list every target; a new `src/` category without a job pair fails the plan in seconds. The
reasoning behind this layout is in the header comment of `live.yml`.

Every job runs the same `yarn test:live` entry point a local run does, so the flake semantics
are identical. The two flags CI adds are `--compile-only` (build the slice and stop) and
`--prebuilt` (run against a downloaded build, and abort rather than compile if it is
incomplete). A `filter` input narrows the matrix as well as the run, matched exactly as vitest
does. Each job's verdict lands in its job summary; JSON reports upload as a
`live-reports-<target>-<file>` artifact on every run, and service and worker logs as
`live-logs-<target>-<file>` on failure.

The workflow holds no logic: matrix resolution and nightly reporting live in
[`scripts/live-ci.ts`](scripts/live-ci.ts), so both are unit tested and runnable locally.

```bash
yarn test:scripts # dry unit tests for scripts/live and scripts/ci
yarn types:scripts # type-check them (also part of `yarn types`)
```
2 changes: 1 addition & 1 deletion scripts/live/targets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export function resolvePlan(
const scoped =
!integration && args.length > 0 && allCategories.includes(args[0]);

// The first positional arg always names the target (CONTRIBUTING.md). If it is
// The first positional arg always names the target (TESTING.md). If it is
// present but names neither an active live category — an excluded one like
// `archive`, or a typo — nor `integration`, reject it here so the caller can
// fail fast, BEFORE the expensive compile / env-up / harness-smoke setup (no
Expand Down
Loading