Skip to content

Commit 971666c

Browse files
authored
test: compact-deployer examples and integration suite (#109)
* test(deployer): add examples and integration-test suite Add the fungible-token deploy walkthrough under examples/ and the end-to-end integration suite under tests/integrations/, both built on the compact-deployer tool. * examples/fungible-token: a TokenExample contract wrapping the OZ Compact FungibleToken module, with deploy scripts exercising the common constructor-arg types and `compact-deploy` CLI invocations. * tests/integrations: vitest specs covering deploy flows, dry-run, history isolation/rotation, wallet lifecycle, keystore and error paths, plus a docker local-stack harness driven by the top-level Makefile. * Re-add the `examples/*` workspace and the integration/env make shortcuts to the root package.json, and the root compact.toml used for real-network deploys. Both source trees are compiled with `compact compile +0.31.1`, the compiler that matches the deployer's pinned compact-runtime 0.16.0. An artifact from the default compactc fails at submit with a version mismatch, so the version is a Makefile variable rather than implicit. The root `types` script now also runs tsc over tests/integrations. That tree is not a workspace package, so nothing else would type-check it, and vitest only strips its types. The two specs that need `proof_server = "auto"` are skipped: testkit-js boots that container from a `proof-server.yml` in the process CWD, which this repo does not ship. Part 2 of 2 splitting the original deployer PR. Refs: #86 * fix(tests): address CodeRabbit review feedback * integrations/walletPool: evict a wallet build from the cache if it rejects, so a failed `WalletHandler.build`/`provider.start` no longer poisons every later `signerFor(alias)` call with the same rejection. * integrations/local-env: pin proof-server and indexer-standalone to release tags (8.0.3 and 4.0.1) rather than `:latest`, so the stack matches the ledger-v8 versions the deployer supports and does not move under the suite. Drop the proof-server container healthcheck too: that image is distroless, so no probe can run in it and the container never reports healthy. `make env-up` polls /version from the host instead. Refs: #86 * fix(examples): point testnet proof_server at localhost "auto" boots a testkit container from a proof-server.yml in the working directory; nothing in this repo ships one, so the preview and preprod blocks could never resolve a proof server. * test(integrations): assert the seeded private state The PrivateCounter spec inferred both deploy-pipeline paths from a green result. It now owns the private-state provider and reads the seed back, and asserts whole shapes instead of probing fields. * docs(examples): name the vendored compact-contracts commit The three vendored modules claimed v0.0.1-alpha.1; they are byte-identical to compact-contracts 19b36a74. The header now names the commit so a refresh has a diff base, and TokenExample drops the unused Maybe export. * docs(examples): link the CLI flag list, drop sync timing The preview timing contradicted the null-route callout two lines below, and the hand-copied flag list was a second source of truth for the deployer README's. * refactor(integrations): one source for stack + seeds The MIDNIGHT_* overrides could point pool wallets at a different stack than the one compact.toml deploys against, and the seed table restated the deployer's LOCAL_PREFUNDED_SEEDS. Aliases now index into that export, and getSharedPool() drops the env argument it ignored after the first call. * test(integrations): assert error text, keep the pool warm walletPool.spec reset the shared pool in afterAll, forcing every later spec to re-sync its wallets; it now owns a pool. The error specs promised messages naming the offending key and asserted only the class. * build: make types and test-integration self-sufficient Both needed packages/deployer/dist to exist already. `types` now runs the build task first and `test-integration` takes build as a prerequisite. The Ctrl+C teardown ran twice because the EXIT handler fires after the INT one; trap EXIT alone. @tsconfig/node24 pinned exact like the other root dev deps. * chore: drop the root compact.toml Nothing consumed it: the integration suite and the example each carry their own config. Its `signing_key_file` pointed at a gitignored path that does not exist, and because `CompactConfig.load()` walks upward it silently became the config for any bare `compact-deploy` run inside `packages/*`. * test(integrations): un-skip the proof-server specs The `auto` path works now that the deployer ships its compose file, so both specs run against a real container instead of being skipped. Each asserts that no `proof-server_*` container is left running: the containers take a random host port, so a leak collides with nothing and a later deploy succeeding proves nothing about cleanup on its own. That assertion is also why the async-dispose spec no longer redeploys. As the suite's first transaction on a fresh chain, that deploy failed on dust the genesis wallet had not accrued yet. The example's testnet blocks go back to `"auto"`, so nothing there needs a proof server started by hand. * docs(integrations): correct the proof-server healthcheck note The image has a shell; it only lacks curl. Say that instead of calling it distroless. * docs(examples): say the vendored modules carry local headers * build: recompile fixtures when the compiler pin changes The artifact targets only depended on their .compact sources, so a COMPACTC_VERSION bump kept artifacts from the previous compiler. * test(integrations): assert prepare leaves an injected wallet unstarted * fix(integrations): await the deploy inside the await-using scope Returning the bare promise let Deployer's asyncDispose run before the deploy settled, tearing down the wallet and any auto proof-server container while proving was still in flight. * fix(integrations): publish the local stack on loopback only Every consumer targets 127.0.0.1, so binding on 0.0.0.0 only exposed the dev node and the insecure-credential indexer to the LAN. * build: make clean drop fixture artifacts and logs
1 parent 66d6f5f commit 971666c

44 files changed

Lines changed: 2841 additions & 4 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ managed/
3333
# never gets committed — including under examples/, where the walkthrough
3434
# expects you to compile the contract yourself before deploying.
3535
artifacts/
36+
# compact-compiler's per-source circuit-info cache, written next to the
37+
# .compact sources it measures.
38+
.circuit-info.json
3639
midnight-level-db
3740
compactc
3841

Makefile

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
# compact-tools — top-level Makefile.
2+
#
3+
# Single entry point for build / lint / test pipelines and for the
4+
# integration-test docker stack. Workspace tasks delegate to `yarn`
5+
# (which delegates to turbo); docker + compactc orchestration lives
6+
# here because Make's recipes run in /bin/sh and support `trap`,
7+
# which yarn's built-in shell does not.
8+
9+
INTEGRATION_DIR := tests/integrations
10+
COMPOSE_FILE := $(INTEGRATION_DIR)/local-env.yml
11+
LOGS_DIR := $(INTEGRATION_DIR)/logs
12+
SERVICES := proof-server indexer node
13+
PROOF_SERVER_URL := http://127.0.0.1:6300
14+
15+
# The deployer pins compact-runtime 0.16.0; the default compactc (0.34.x)
16+
# emits code for 0.19.0 and the deploy then fails with `Version mismatch`.
17+
# See "Supported stack" in packages/deployer/README.md.
18+
COMPACTC_VERSION := 0.31.1
19+
20+
# One marker file per fixture: Make uses mtime against the .compact
21+
# source to decide whether a re-compile is needed, so `make compile`
22+
# is a no-op when nothing changed (poor man's build cache, free).
23+
COUNTER_OUT := $(INTEGRATION_DIR)/fixtures/artifacts/Counter/contract/index.js
24+
PRIVATE_OUT := $(INTEGRATION_DIR)/fixtures/artifacts/PrivateCounter/contract/index.js
25+
26+
.PHONY: help \
27+
build test types lint lint-fix clean \
28+
env-up env-down env-logs env-status \
29+
compile test-integration
30+
31+
help: ## Show this help.
32+
@echo "compact-tools — common targets"
33+
@echo ""
34+
@echo " Workspace tasks (delegate to yarn → turbo)"
35+
@echo " make build Build all workspace packages"
36+
@echo " make test Run unit tests"
37+
@echo " make types Type-check all packages"
38+
@echo " make lint Lint with biome"
39+
@echo " make lint-fix Lint and auto-fix"
40+
@echo " make clean Clean build artifacts"
41+
@echo ""
42+
@echo " Integration-test docker stack"
43+
@echo " make env-up Start local Midnight stack (proof-server + indexer + node)"
44+
@echo " make env-down Stop local stack and remove volumes"
45+
@echo " make env-logs Tail all docker stack logs"
46+
@echo " make env-status Show docker container status"
47+
@echo ""
48+
@echo " Integration-test fixtures + run"
49+
@echo " make compile Compile fixture contracts with compactc $(COMPACTC_VERSION)"
50+
@echo " make test-integration End-to-end: env-up → compile → vitest → env-down"
51+
52+
# ── Workspace tasks ────────────────────────────────────────────────────
53+
54+
build:
55+
yarn build
56+
57+
test:
58+
yarn test
59+
60+
types:
61+
yarn types
62+
63+
lint:
64+
yarn lint
65+
66+
lint-fix:
67+
yarn lint:fix
68+
69+
clean:
70+
yarn clean
71+
rm -rf $(INTEGRATION_DIR)/fixtures/artifacts logs
72+
73+
# ── Integration-test docker stack ──────────────────────────────────────
74+
75+
env-up: env-down
76+
docker compose -f $(COMPOSE_FILE) up -d --wait
77+
@echo "Waiting for the proof server to answer on $(PROOF_SERVER_URL)/version ..."
78+
@i=0; until curl -sf $(PROOF_SERVER_URL)/version >/dev/null; do \
79+
i=$$((i+1)); \
80+
if [ $$i -ge 60 ]; then echo "proof server did not come up"; exit 1; fi; \
81+
sleep 2; \
82+
done
83+
@mkdir -p $(LOGS_DIR)
84+
@for svc in $(SERVICES); do \
85+
docker compose -f $(COMPOSE_FILE) logs -f --no-log-prefix $$svc > $(LOGS_DIR)/$$svc.log 2>&1 & \
86+
done
87+
@echo "Logs streaming to $(LOGS_DIR)/"
88+
89+
env-down:
90+
@-pkill -f "docker compose -f $(COMPOSE_FILE) logs" 2>/dev/null || true
91+
docker compose -f $(COMPOSE_FILE) down -v
92+
93+
env-logs:
94+
tail -f $(LOGS_DIR)/*.log
95+
96+
env-status:
97+
docker compose -f $(COMPOSE_FILE) ps
98+
99+
# ── Integration-test fixtures ──────────────────────────────────────────
100+
#
101+
# Each fixture depends on its .compact source and on this Makefile, so a
102+
# COMPACTC_VERSION bump recompiles instead of reusing old artifacts.
103+
104+
compile: $(COUNTER_OUT) $(PRIVATE_OUT)
105+
106+
$(COUNTER_OUT): $(INTEGRATION_DIR)/fixtures/Counter.compact Makefile
107+
compact compile +$(COMPACTC_VERSION) $< $(INTEGRATION_DIR)/fixtures/artifacts/Counter
108+
109+
$(PRIVATE_OUT): $(INTEGRATION_DIR)/fixtures/PrivateCounter.compact Makefile
110+
compact compile +$(COMPACTC_VERSION) $< $(INTEGRATION_DIR)/fixtures/artifacts/PrivateCounter
111+
112+
# ── End-to-end integration test ────────────────────────────────────────
113+
#
114+
# Runs the whole pipeline in one /bin/sh invocation (note the `\`
115+
# continuations) so the `trap` survives across the chain. EXIT alone
116+
# covers success, failure, and Ctrl+C; adding INT / TERM ran `env-down`
117+
# twice on Ctrl+C, because the EXIT handler fires after the signal
118+
# handler.
119+
120+
# `build` is a prerequisite, not part of the chain: the specs import the
121+
# deployer from `dist/`, and a build failure should not bring a stack up
122+
# only to tear it down again.
123+
test-integration: build
124+
@trap '$(MAKE) env-down' EXIT; \
125+
$(MAKE) env-up && \
126+
$(MAKE) compile && \
127+
yarn vitest run --config $(INTEGRATION_DIR)/vitest.config.ts

examples/README.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# compact-tools examples
2+
3+
Runnable, copy-pasteable starting points for `compact-deployer`. Each example is self-contained: its own `compact.toml`, its own `package.json`, its own compiled artifact, and its own hand-written deploy script using the programmatic deployer API.
4+
5+
## Available examples
6+
7+
| Example | What it covers |
8+
|---|---|
9+
| [fungible-token/](./fungible-token/) | Deploys a small ERC20-flavoured contract wrapping OpenZeppelin Compact's `FungibleToken` module. Constructor exercises every common Compact primitive type: strings, `Uint<8/32/64/128>`, `Boolean`, `Bytes<8/32>`. |
10+
11+
More to come (private state + witnesses, multisig patterns, programmatic API).
12+
13+
## Conventions
14+
15+
- Each example builds and runs on Node 24+.
16+
- Compiled artifacts are gitignored. Run `yarn compile` in the example before deploying; it needs the `compact` toolchain with compiler 0.31.1 installed.
17+
- `deploy/*.signingkey` files are gitignored. Generate per the example README.
18+
- `.states/` (wallet cache) and `deployments/` (deploy records) are gitignored.
19+
- Compact-contracts modules (`FungibleToken`, `Initializable`, `Utils`) are copied from [openzeppelin/compact-contracts](https://github.com/openzeppelin/compact-contracts) at commit `19b36a74`, not submodules. Each copy carries a local header naming that commit; refresh by recopying from a newer one and updating the header.
20+
- These examples must stay compilable on compactc 0.31.1, which is what the deployer's compact-runtime 0.16.0 pin requires. Compact-contracts `main` has since moved to compiler 0.34 / runtime 0.19, so its newer sources will not drop in unchanged.
21+
22+
## Setup
23+
24+
Each example is a yarn workspace member, so a single root-level install wires every binary (`compact-compiler`, `compact-deploy`) into the example. From the repo root:
25+
26+
```bash
27+
yarn install
28+
yarn build
29+
```
30+
31+
After that:
32+
33+
```bash
34+
cd examples/<name>
35+
yarn compile # rebuild the artifact if you edit a .compact file
36+
yarn deploy:local # run the example
37+
```

examples/fungible-token/README.md

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
# TokenExample — `compact-deployer` walkthrough with a rich constructor
2+
3+
Deploys a small ERC20-flavoured contract built on the OpenZeppelin Compact `FungibleToken` module. The example shows two ways to drive the deployer:
4+
5+
1. **A TS deploy script** that imports `runDeploy()` from `@openzeppelin/compact-deployer/run-deploy` and passes constructor args inline as native JS values.
6+
2. **The `compact-deploy` CLI** binary from `@openzeppelin/compact-cli`, which reads args from a `.args.mjs` module referenced in `compact.toml`.
7+
8+
Both end up calling the same deployer code; pick whichever fits your workflow.
9+
10+
The constructor exercises every common Compact primitive type:
11+
12+
| Constructor arg | Compact type | JS type |
13+
|---|---|---|
14+
| `_name` | `Opaque<"string">` | `string` |
15+
| `_symbol` | `Opaque<"string">` | `string` |
16+
| `_decimals` | `Uint<8>` | `bigint` |
17+
| `_treasury` | `Bytes<32>` | `Uint8Array(32)` |
18+
| `_maxSupply` | `Uint<128>` | `bigint` |
19+
| `_feeBps` | `Uint<32>` | `bigint` |
20+
| `_quorum` | `Uint<64>` | `bigint` |
21+
| `_isMintable` | `Boolean` | `boolean` |
22+
| `_tag` | `Bytes<8>` | `Uint8Array(8)` |
23+
24+
## What's in here
25+
26+
```
27+
fungible-token/
28+
contracts/
29+
TokenExample.compact wrapper with the rich constructor
30+
token/FungibleToken.compact vendored from compact-contracts
31+
security/Initializable.compact vendored from compact-contracts
32+
utils/Utils.compact vendored from compact-contracts
33+
artifacts/TokenExample/ compiler output (gitignored; you generate this)
34+
compact.toml deployer config (3 networks defined)
35+
deploy/
36+
deployTokenExample.ts the TS deploy script (path #1)
37+
TokenExample.args.mjs args module read by the CLI (path #2)
38+
TokenExample.signingkey you generate this (gitignored)
39+
deployments/ deployer writes here on success (gitignored)
40+
package.json a workspace member: depends on
41+
@openzeppelin/compact-deployer +
42+
compact-cli via `workspace:^`
43+
```
44+
45+
## Prerequisites
46+
47+
- Node 24+
48+
- Docker — for the local Midnight stack, and for `proof_server = "auto"` on the testnets
49+
- The `compact` toolchain with compiler 0.31.1 available (`compact list`). The deployer pins compact-runtime 0.16.0; an artifact from the default compactc fails at submit with a `Version mismatch`.
50+
- A one-time root setup: `yarn install && yarn build` from the repo root. This is a yarn workspace, so binaries like `compact-compiler` and `compact-deploy` resolve automatically inside this folder.
51+
52+
## Run it
53+
54+
```bash
55+
cd examples/fungible-token
56+
57+
# 1. Compile the contract — artifacts/ is gitignored, so generate it first.
58+
yarn compile
59+
60+
# 2. Generate a per-contract signing key.
61+
head -c 32 /dev/urandom | xxd -p -c 32 > deploy/TokenExample.signingkey
62+
63+
# 3. Start the local Midnight stack (from the repo root).
64+
make env-up
65+
66+
# 4. Pick a path — see below.
67+
```
68+
69+
### Path 1 — TS deploy script (args inline)
70+
71+
```bash
72+
yarn deploy:local # node deploy/deployTokenExample.ts
73+
yarn deploy:preview # …--network preview --sync-timeout 1800
74+
yarn deploy:preprod # …--network preprod --sync-timeout 7200
75+
```
76+
77+
[`deploy/deployTokenExample.ts`](deploy/deployTokenExample.ts) is the whole script:
78+
79+
```ts
80+
import { runDeploy } from '@openzeppelin/compact-deployer/run-deploy';
81+
import { Contract } from '../artifacts/TokenExample/contract/index.js';
82+
83+
await runDeploy(Contract)(
84+
'OpenZeppelin Example Token', // editor: "_name_2: string"
85+
'OZE', // editor: "_symbol_2: string"
86+
18n, // editor: "_decimals_2: bigint"
87+
new Uint8Array(32).fill(0xab),
88+
1_000_000_000_000_000_000_000_000n,
89+
250n, 7n, true,
90+
new Uint8Array([0xde, 0xad, 0xbe, 0xef, 0xca, 0xfe, 0xba, 0xbe]),
91+
);
92+
```
93+
94+
The curried form names the contract once via the imported `Contract` class — the deployer matches it to `[contracts.TokenExample]` in `compact.toml` by class identity, so no string repetition. Constructor args are typed function parameters: each comma triggers TypeScript signature help showing the next param's name and type.
95+
96+
To pass extra deploy options (network, dry-run, …), supply them as the second arg:
97+
98+
```ts
99+
await runDeploy(Contract, { network: 'preview', dryRun: true })(
100+
'OpenZeppelin Example Token', 'OZE', 18n, /**/
101+
);
102+
```
103+
104+
`runDeploy()` parses the same flags as the `compact-deploy` CLI out of `process.argv` and uses them as defaults; both go through `parseDeployArgv`, so the flag list under ["CLI" in `packages/deployer/README.md`](../../packages/deployer/README.md#cli) is the one to read. Explicit options on the call win.
105+
106+
Alternative call shapes:
107+
- `runDeploy({ contract: 'TokenExample', args: [...] })` — options-object form. Use when args come from `compact.toml`, when one `compact.toml` has multiple entries for the same Contract class, or for programmatic flows.
108+
- `runDeploy({ contract: 'TokenExample', args: constructorArgs(Contract, ...) })` — keeps the per-comma editor hints inside an options-object call.
109+
- Named-object args (`args: { _name: '…', … }`) — full autocomplete, but the interface has to be hand-written until compactc exports one.
110+
111+
### Path 2 — `compact-deploy` CLI (args in a separate module)
112+
113+
```bash
114+
yarn cli:local # compact-deploy TokenExample --network local
115+
yarn cli:preview # compact-deploy TokenExample --network preview …
116+
yarn cli:preprod # compact-deploy TokenExample --network preprod …
117+
```
118+
119+
`compact.toml` already points at the args module:
120+
121+
```toml
122+
[contracts.TokenExample]
123+
artifact = "TokenExample"
124+
signing_key_file = "deploy/TokenExample.signingkey"
125+
args = { module = "./deploy/TokenExample.args.mjs", export = "args" }
126+
```
127+
128+
[`deploy/TokenExample.args.mjs`](deploy/TokenExample.args.mjs) exports the same JS values as Path 1. The CLI doesn't need a script — `compact-deploy TokenExample --network <name>` reads everything from `compact.toml`.
129+
130+
### When to pick which
131+
132+
| Picking… | When |
133+
|---|---|
134+
| Path 1 (script) | The deploy logic itself is the moving part. Easy to add post-deploy work (seed state, run callTx, write a custom record) in the same file. |
135+
| Path 2 (CLI) | The deploy logic is fixed and only the args vary per network or per build. Lighter footprint — no JS script to maintain. |
136+
137+
`runDeploy()` actually accepts the same `args` field that you'd put in `compact.toml`, so Path 1 can read from a `.args.mjs` too (drop the `args:` field from the script call and the TOML ref takes over).
138+
139+
## Type-by-type cheat sheet
140+
141+
| Compact | JS |
142+
|---|---|
143+
| `Opaque<"string">` | `string` |
144+
| `Uint<N>` (any width) | `bigint` (use the `n` suffix: `18n`, `250n`). The compiler emits every `Uint<N>` as `bigint`. |
145+
| `Boolean` | `boolean` |
146+
| `Bytes<N>` | `new Uint8Array(N)` of length exactly `N` |
147+
| `Vector<N, T>` | array of length exactly `N` |
148+
| `Maybe<T>` | `{ is_some: true, value: T }` or `{ is_some: false, value: <zero-T> }` |
149+
| `Either<L, R>` | `{ is_left: true, left: L, right: <zero-R> }` or mirror with `is_left: false` |
150+
151+
`Bytes<N>` values must be exactly `N` bytes — neither path pads or truncates.
152+
153+
## Public testnets (preview, preprod)
154+
155+
Both testnet blocks in `compact.toml` set `proof_server = "auto"`, so there is nothing to start first: the deployer boots a proof-server container from a compose file it ships and stops it after the deploy. Docker has to be running. To reuse a server you already have, replace `"auto"` with its URL.
156+
157+
```bash
158+
yarn deploy:preview # or yarn cli:preview
159+
yarn deploy:preprod # or yarn cli:preprod
160+
```
161+
162+
The deployer caches both shielded + dust state under `.states/`, so only the first sync on a network is slow; subsequent runs are near-instant.
163+
164+
> Preview's endpoints are null-routed. Preprod is reachable but a cold first sync runs ~37 min. See the deployer's "Known issues" section in [`packages/deployer/README.md`](../../packages/deployer/README.md).
165+
166+
## Recompile the contract
167+
168+
If you edit `contracts/TokenExample.compact` (or any vendored file under `contracts/`):
169+
170+
```bash
171+
yarn compile
172+
```
173+
174+
This runs the workspace's `compact-compiler` (the bin from `@openzeppelin/compact-cli`) over `contracts/` with compiler 0.31.1 and emits a hierarchical artifact tree under `artifacts/`. The `artifacts/` tree is gitignored: regenerate it locally, don't commit it.
175+
176+
## Cleanup
177+
178+
```bash
179+
make env-down # from the repo root
180+
rm -rf .states deployments deploy/TokenExample.signingkey
181+
```
182+
183+
## Where to look next
184+
185+
- [`packages/deployer/README.md`](../../packages/deployer/README.md) — every CLI flag, keystore format, current known-issues list.
186+
- `contracts/token/FungibleToken.compact` — the full ERC20-ish surface this wrapper delegates to (`transfer`, `_mint`, `allowance`, etc.). Wire more circuits into `TokenExample.compact` to expose them.

0 commit comments

Comments
 (0)