Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ managed/
# never gets committed — including under examples/, where the walkthrough
# expects you to compile the contract yourself before deploying.
artifacts/
# compact-compiler's per-source circuit-info cache, written next to the
# .compact sources it measures.
.circuit-info.json
midnight-level-db
compactc

Expand Down
127 changes: 127 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# compact-tools — top-level Makefile.
#
# Single entry point for build / lint / test pipelines and for the
# integration-test docker stack. Workspace tasks delegate to `yarn`
# (which delegates to turbo); docker + compactc orchestration lives
# here because Make's recipes run in /bin/sh and support `trap`,
# which yarn's built-in shell does not.

INTEGRATION_DIR := tests/integrations
COMPOSE_FILE := $(INTEGRATION_DIR)/local-env.yml
LOGS_DIR := $(INTEGRATION_DIR)/logs
SERVICES := proof-server indexer node
PROOF_SERVER_URL := http://127.0.0.1:6300

# The deployer pins compact-runtime 0.16.0; the default compactc (0.34.x)
# emits code for 0.19.0 and the deploy then fails with `Version mismatch`.
# See "Supported stack" in packages/deployer/README.md.
COMPACTC_VERSION := 0.31.1

# One marker file per fixture: Make uses mtime against the .compact
# source to decide whether a re-compile is needed, so `make compile`
# is a no-op when nothing changed (poor man's build cache, free).
COUNTER_OUT := $(INTEGRATION_DIR)/fixtures/artifacts/Counter/contract/index.js
PRIVATE_OUT := $(INTEGRATION_DIR)/fixtures/artifacts/PrivateCounter/contract/index.js

.PHONY: help \
build test types lint lint-fix clean \
env-up env-down env-logs env-status \
compile test-integration

help: ## Show this help.
@echo "compact-tools — common targets"
@echo ""
@echo " Workspace tasks (delegate to yarn → turbo)"
@echo " make build Build all workspace packages"
@echo " make test Run unit tests"
@echo " make types Type-check all packages"
@echo " make lint Lint with biome"
@echo " make lint-fix Lint and auto-fix"
@echo " make clean Clean build artifacts"
@echo ""
@echo " Integration-test docker stack"
@echo " make env-up Start local Midnight stack (proof-server + indexer + node)"
@echo " make env-down Stop local stack and remove volumes"
@echo " make env-logs Tail all docker stack logs"
@echo " make env-status Show docker container status"
@echo ""
@echo " Integration-test fixtures + run"
@echo " make compile Compile fixture contracts with compactc $(COMPACTC_VERSION)"
@echo " make test-integration End-to-end: env-up → compile → vitest → env-down"

# ── Workspace tasks ────────────────────────────────────────────────────

build:
yarn build

test:
yarn test

types:
yarn types

lint:
yarn lint

lint-fix:
yarn lint:fix

clean:
yarn clean
rm -rf $(INTEGRATION_DIR)/fixtures/artifacts logs

# ── Integration-test docker stack ──────────────────────────────────────

env-up: env-down
docker compose -f $(COMPOSE_FILE) up -d --wait
@echo "Waiting for the proof server to answer on $(PROOF_SERVER_URL)/version ..."
@i=0; until curl -sf $(PROOF_SERVER_URL)/version >/dev/null; do \
i=$$((i+1)); \
if [ $$i -ge 60 ]; then echo "proof server did not come up"; exit 1; fi; \
sleep 2; \
done
@mkdir -p $(LOGS_DIR)
@for svc in $(SERVICES); do \
docker compose -f $(COMPOSE_FILE) logs -f --no-log-prefix $$svc > $(LOGS_DIR)/$$svc.log 2>&1 & \
done
@echo "Logs streaming to $(LOGS_DIR)/"

env-down:
@-pkill -f "docker compose -f $(COMPOSE_FILE) logs" 2>/dev/null || true
docker compose -f $(COMPOSE_FILE) down -v

env-logs:
tail -f $(LOGS_DIR)/*.log

env-status:
docker compose -f $(COMPOSE_FILE) ps

# ── Integration-test fixtures ──────────────────────────────────────────
#
# Each fixture depends on its .compact source and on this Makefile, so a
# COMPACTC_VERSION bump recompiles instead of reusing old artifacts.

compile: $(COUNTER_OUT) $(PRIVATE_OUT)

$(COUNTER_OUT): $(INTEGRATION_DIR)/fixtures/Counter.compact Makefile
compact compile +$(COMPACTC_VERSION) $< $(INTEGRATION_DIR)/fixtures/artifacts/Counter

$(PRIVATE_OUT): $(INTEGRATION_DIR)/fixtures/PrivateCounter.compact Makefile
compact compile +$(COMPACTC_VERSION) $< $(INTEGRATION_DIR)/fixtures/artifacts/PrivateCounter

# ── End-to-end integration test ────────────────────────────────────────
#
# Runs the whole pipeline in one /bin/sh invocation (note the `\`
# continuations) so the `trap` survives across the chain. EXIT alone
# covers success, failure, and Ctrl+C; adding INT / TERM ran `env-down`
# twice on Ctrl+C, because the EXIT handler fires after the signal
# handler.

# `build` is a prerequisite, not part of the chain: the specs import the
# deployer from `dist/`, and a build failure should not bring a stack up
# only to tear it down again.
test-integration: build
@trap '$(MAKE) env-down' EXIT; \
$(MAKE) env-up && \
$(MAKE) compile && \
yarn vitest run --config $(INTEGRATION_DIR)/vitest.config.ts
37 changes: 37 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# compact-tools examples

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.

## Available examples

| Example | What it covers |
|---|---|
| [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>`. |

More to come (private state + witnesses, multisig patterns, programmatic API).

## Conventions

- Each example builds and runs on Node 24+.
- Compiled artifacts are gitignored. Run `yarn compile` in the example before deploying; it needs the `compact` toolchain with compiler 0.31.1 installed.
- `deploy/*.signingkey` files are gitignored. Generate per the example README.
- `.states/` (wallet cache) and `deployments/` (deploy records) are gitignored.
- 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.
- 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.

## Setup

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:

```bash
yarn install
yarn build
```

After that:

```bash
cd examples/<name>
yarn compile # rebuild the artifact if you edit a .compact file
yarn deploy:local # run the example
```
186 changes: 186 additions & 0 deletions examples/fungible-token/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
# TokenExample — `compact-deployer` walkthrough with a rich constructor

Deploys a small ERC20-flavoured contract built on the OpenZeppelin Compact `FungibleToken` module. The example shows two ways to drive the deployer:

1. **A TS deploy script** that imports `runDeploy()` from `@openzeppelin/compact-deployer/run-deploy` and passes constructor args inline as native JS values.
2. **The `compact-deploy` CLI** binary from `@openzeppelin/compact-cli`, which reads args from a `.args.mjs` module referenced in `compact.toml`.

Both end up calling the same deployer code; pick whichever fits your workflow.

The constructor exercises every common Compact primitive type:

| Constructor arg | Compact type | JS type |
|---|---|---|
| `_name` | `Opaque<"string">` | `string` |
| `_symbol` | `Opaque<"string">` | `string` |
| `_decimals` | `Uint<8>` | `bigint` |
| `_treasury` | `Bytes<32>` | `Uint8Array(32)` |
| `_maxSupply` | `Uint<128>` | `bigint` |
| `_feeBps` | `Uint<32>` | `bigint` |
| `_quorum` | `Uint<64>` | `bigint` |
| `_isMintable` | `Boolean` | `boolean` |
| `_tag` | `Bytes<8>` | `Uint8Array(8)` |

## What's in here

```
fungible-token/
contracts/
TokenExample.compact wrapper with the rich constructor
token/FungibleToken.compact vendored from compact-contracts
security/Initializable.compact vendored from compact-contracts
utils/Utils.compact vendored from compact-contracts
artifacts/TokenExample/ compiler output (gitignored; you generate this)
compact.toml deployer config (3 networks defined)
deploy/
deployTokenExample.ts the TS deploy script (path #1)
TokenExample.args.mjs args module read by the CLI (path #2)
TokenExample.signingkey you generate this (gitignored)
deployments/ deployer writes here on success (gitignored)
package.json a workspace member: depends on
@openzeppelin/compact-deployer +
compact-cli via `workspace:^`
```

## Prerequisites

- Node 24+
- Docker — for the local Midnight stack, and for `proof_server = "auto"` on the testnets
- 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`.
- 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.

## Run it

```bash
cd examples/fungible-token

# 1. Compile the contract — artifacts/ is gitignored, so generate it first.
yarn compile

# 2. Generate a per-contract signing key.
head -c 32 /dev/urandom | xxd -p -c 32 > deploy/TokenExample.signingkey

# 3. Start the local Midnight stack (from the repo root).
make env-up

# 4. Pick a path — see below.
```

### Path 1 — TS deploy script (args inline)

```bash
yarn deploy:local # node deploy/deployTokenExample.ts
yarn deploy:preview # …--network preview --sync-timeout 1800
yarn deploy:preprod # …--network preprod --sync-timeout 7200
```

[`deploy/deployTokenExample.ts`](deploy/deployTokenExample.ts) is the whole script:

```ts
import { runDeploy } from '@openzeppelin/compact-deployer/run-deploy';
import { Contract } from '../artifacts/TokenExample/contract/index.js';

await runDeploy(Contract)(
'OpenZeppelin Example Token', // editor: "_name_2: string"
'OZE', // editor: "_symbol_2: string"
18n, // editor: "_decimals_2: bigint"
new Uint8Array(32).fill(0xab),
1_000_000_000_000_000_000_000_000n,
250n, 7n, true,
new Uint8Array([0xde, 0xad, 0xbe, 0xef, 0xca, 0xfe, 0xba, 0xbe]),
);
```

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.

To pass extra deploy options (network, dry-run, …), supply them as the second arg:

```ts
await runDeploy(Contract, { network: 'preview', dryRun: true })(
'OpenZeppelin Example Token', 'OZE', 18n, /* … */
);
```

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

Alternative call shapes:
- `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.
- `runDeploy({ contract: 'TokenExample', args: constructorArgs(Contract, ...) })` — keeps the per-comma editor hints inside an options-object call.
- Named-object args (`args: { _name: '…', … }`) — full autocomplete, but the interface has to be hand-written until compactc exports one.

### Path 2 — `compact-deploy` CLI (args in a separate module)

```bash
yarn cli:local # compact-deploy TokenExample --network local
yarn cli:preview # compact-deploy TokenExample --network preview …
yarn cli:preprod # compact-deploy TokenExample --network preprod …
```

`compact.toml` already points at the args module:

```toml
[contracts.TokenExample]
artifact = "TokenExample"
signing_key_file = "deploy/TokenExample.signingkey"
args = { module = "./deploy/TokenExample.args.mjs", export = "args" }
```

[`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`.

### When to pick which

| Picking… | When |
|---|---|
| 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. |
| 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. |

`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).

## Type-by-type cheat sheet

| Compact | JS |
|---|---|
| `Opaque<"string">` | `string` |
| `Uint<N>` (any width) | `bigint` (use the `n` suffix: `18n`, `250n`). The compiler emits every `Uint<N>` as `bigint`. |
| `Boolean` | `boolean` |
| `Bytes<N>` | `new Uint8Array(N)` of length exactly `N` |
| `Vector<N, T>` | array of length exactly `N` |
| `Maybe<T>` | `{ is_some: true, value: T }` or `{ is_some: false, value: <zero-T> }` |
| `Either<L, R>` | `{ is_left: true, left: L, right: <zero-R> }` or mirror with `is_left: false` |

`Bytes<N>` values must be exactly `N` bytes — neither path pads or truncates.

## Public testnets (preview, preprod)

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.

```bash
yarn deploy:preview # or yarn cli:preview
yarn deploy:preprod # or yarn cli:preprod
```

The deployer caches both shielded + dust state under `.states/`, so only the first sync on a network is slow; subsequent runs are near-instant.

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

## Recompile the contract

If you edit `contracts/TokenExample.compact` (or any vendored file under `contracts/`):

```bash
yarn compile
```

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.

## Cleanup

```bash
make env-down # from the repo root
rm -rf .states deployments deploy/TokenExample.signingkey
```

## Where to look next

- [`packages/deployer/README.md`](../../packages/deployer/README.md) — every CLI flag, keystore format, current known-issues list.
- `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.
Loading
Loading