OpenZeppelin / OpenZeppelin/compact-tools

Simulator supports a live-mode backend

Open
#75 0 comments 0 reactions 1 assignee View on GitHub

@0xisk is already working on this.

Since Apr 24, 2026.

enhancement
Dominant language
TypeScript
Stars
4
Forks
5
Avg merge
15h 28m
Merged PRs (30d)
31

Description

🧐 Motivation

Today every simulator in @openzeppelin-compact/contracts-simulator runs purely in-memory against a CircuitContext. That's fast and deterministic, which is the right default for unit tests, but it only exercises circuit logic in isolation — it never touches a real Midnight node, real provers, real wallets, or real ledger state transitions.

Several things we care about cannot be covered by an in-memory simulator alone:

  • Contract upgradability. Deploying v1, exercising it, upgrading to v2, and verifying state + behavior across the boundary only makes sense against a live node that actually persists ledger state between transactions.
  • End-to-end prover / wallet integration. The in-memory path bypasses proof generation and wallet signing, so regressions in those layers go unnoticed until integration time.
  • Multi-caller flows. Scenarios with several funded accounts interacting with the same deployed contract (role grants, transfers, access checks across principals) are awkward to model in memory and trivial on a live node with a real wallet pool.
  • Confidence that unit-test assertions reflect on-chain reality. Running the same spec against both backends catches divergences between the simulator's model of a circuit and what the node actually executes.

Right now the only way to get any of this is to write a separate, live-node-flavored test file per module, with its own API surface that mirrors the simulator's. That duplication is the root problem: the public API of each contract ends up encoded in two places, and the two drift.

📝 Details

Make the existing per-module simulator classes the single home for each contract's test-facing API, and let them run against either an in-memory context or a real deployed contract on a local Midnight node — same methods, same spec files, selected at fixture-construction time.

Proposal

Introduce a Backend abstraction in @openzeppelin-compact/contracts-simulator:

  • DryBackend — wraps the existing in-memory CircuitContext path.
  • LiveBackend<C> — wraps a DeployedContract<C>, MidnightProviders, and a wallet (plus an optional address-keyed wallet pool for .as(alias) / setPersistentCaller(alias) support).

Each simulator method returns Promise<T> in both modes (dry mode wraps sync logic in async, live mode awaits network calls). Tests write await simulator.foo() uniformly; no conditional types, no runtime branching in spec code. Backend selection happens at fixture-construction time, driven by MIDNIGHT_BACKEND=dry|live.

A new factory createBackendSimulator(...) lives alongside the existing createSimulator(...); modules opt in one at a time by swapping the factory call and wrapping their method return types in Promise<T>. No flag day.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.