Skip to content

Simulator supports a live-mode backend #75

Description

@0xisk

🧐 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type

Projects

Relationships

None yet

Development

No branches or pull requests

Issue actions