From 92b46a85182e04ba2f72b240cac7127b07c73d1e Mon Sep 17 00:00:00 2001 From: andrew Date: Sat, 16 Aug 2025 20:20:15 -0300 Subject: [PATCH 01/23] add wrapAsEither circuits --- contracts/src/utils/Utils.compact | 46 +++++++++++++++++++ .../src/utils/test/mocks/MockUtils.compact | 16 +++++++ .../utils/test/simulators/UtilsSimulator.ts | 28 +++++++++++ contracts/src/utils/test/utils.test.ts | 29 ++++++++++++ 4 files changed, 119 insertions(+) diff --git a/contracts/src/utils/Utils.compact b/contracts/src/utils/Utils.compact index 3528e10e..b7291176 100644 --- a/contracts/src/utils/Utils.compact +++ b/contracts/src/utils/Utils.compact @@ -66,6 +66,52 @@ module Utils { return !keyOrAddress.is_left; } + /** + * @description Wraps a value as the `L` variant of a disjoint union (`Either`), + * following Compact conventions. + * + * Sets `is_left` to true, assigns the provided value to `left`, and sets `right` to `default` per convention. + * + * This helper is useful when you already have a value of type `L`, but the circuit interface expects an `Either`. + * It avoids manually constructing the full struct and ensures consistent formatting. + * + * @template L - Type of the Left variant. + * @template R - Type of the Right variant. + * + * @param {L} left - The value to wrap as the Left variant. + * @returns {Either} A disjoint union (`Either`) with the value in the `L` variant. + */ + export pure circuit wrapAsEitherLeft(left: L): Either { + return Either { + is_left: true, + left: left, + right: default + }; + } + + /** + * @description Wraps a value as the `R` variant of a disjoint union (`Either`), + * following Compact conventions. + * + * Sets `is_left` to false, assigns the provided value to `right`, and sets `left` to `default` per convention. + * + * This helper is useful when you already have a value of type `R`, but the circuit interface expects an `Either`. + * It avoids manually constructing the full struct and ensures consistent formatting. + * + * @template L - Type of the Left variant. + * @template R - Type of the Right variant. + * + * @param {R} right - The value to wrap as the `R` variant. + * @returns {Either} A disjoint union (`Either`) with the value in the `R` variant. + */ + export pure circuit wrapAsEitherRight(right: R): Either { + return Either { + is_left: false, + left: default, + right: right + }; + } + /** * @description A helper function that returns the empty string: "". * diff --git a/contracts/src/utils/test/mocks/MockUtils.compact b/contracts/src/utils/test/mocks/MockUtils.compact index 54fd45f3..c3b66d64 100644 --- a/contracts/src/utils/test/mocks/MockUtils.compact +++ b/contracts/src/utils/test/mocks/MockUtils.compact @@ -25,6 +25,22 @@ export pure circuit isContractAddress(keyOrAddress: Either { + return Utils_wrapAsEitherLeft(pk); +} + +// Find a better way to test different combinations +// other than creating a circuit for each pair +export pure circuit wrapAsEitherPkOrAddressRight( + address: ContractAddress, +): Either { + return Utils_wrapAsEitherRight(address); +} + export pure circuit emptyString(): Opaque<"string"> { return Utils_emptyString(); } diff --git a/contracts/src/utils/test/simulators/UtilsSimulator.ts b/contracts/src/utils/test/simulators/UtilsSimulator.ts index 715569f8..5f46223e 100644 --- a/contracts/src/utils/test/simulators/UtilsSimulator.ts +++ b/contracts/src/utils/test/simulators/UtilsSimulator.ts @@ -139,6 +139,34 @@ export class UtilsSimulator ).result; } + /** + * @description Returns `pk` wrapped in an `Either` type. + * @param pk The target value to wrap. + * @returns `Either` with `pk` in the left position. + */ + public wrapAsEitherPkOrAddressLeft( + pk: ZswapCoinPublicKey, + ): Either { + return this.contract.circuits.wrapAsEitherPkOrAddressLeft( + this.circuitContext, + pk, + ).result; + } + + /** + * @description Returns `address` wrapped in an `Either` type. + * @param pk The target value to wrap. + * @returns `Either` with `address` in the right position. + */ + public wrapAsEitherPkOrAddressRight( + address: ContractAddress, + ): Either { + return this.contract.circuits.wrapAsEitherPkOrAddressRight( + this.circuitContext, + address, + ).result; + } + /** * @description A helper function that returns the empty string: "" * @returns The empty string: "" diff --git a/contracts/src/utils/test/utils.test.ts b/contracts/src/utils/test/utils.test.ts index 1398d4d9..52b78acc 100644 --- a/contracts/src/utils/test/utils.test.ts +++ b/contracts/src/utils/test/utils.test.ts @@ -1,4 +1,9 @@ import { describe, expect, it } from 'vitest'; +import type { + ContractAddress, + Either, + ZswapCoinPublicKey, +} from './../../../artifacts/MockUtils/contract/index.cjs'; // Combined imports import { UtilsSimulator } from './simulators/UtilsSimulator.js'; import * as contractUtils from './utils/address.js'; @@ -87,6 +92,30 @@ describe('Utils', () => { }); }); + describe('wrapAsEitherLeft', () => { + it('should wrap pk as left', () => { + const pk = contractUtils.encodeToPK('PK'); + const exp: Either = { + is_left: true, + left: pk, + right: { bytes: new Uint8Array(32).fill(0) }, + }; + expect(contract.wrapAsEitherPkOrAddressLeft(pk)).toEqual(exp); + }); + }); + + describe('wrapAsEitherRight', () => { + it('should wrap address as right', () => { + const address = contractUtils.encodeToPK('ADDRESS'); + const exp: Either = { + is_left: false, + left: { bytes: new Uint8Array(32).fill(0) }, + right: address, + }; + expect(contract.wrapAsEitherPkOrAddressRight(address)).toEqual(exp); + }); + }); + describe('emptyString', () => { it('should return the empty string', () => { expect(contract.emptyString()).toBe(EMPTY_STRING); From 9fc5bbee9cccb63e3c3e6eec6c979af959dfcec9 Mon Sep 17 00:00:00 2001 From: andrew Date: Sat, 16 Aug 2025 21:23:23 -0300 Subject: [PATCH 02/23] add option to compile directory in compact --- compact/src/Compiler.ts | 39 +++++++++++++++++++++++++++++++------- compact/src/runCompiler.ts | 37 ++++++++++++++++++++++++++++++++---- 2 files changed, 65 insertions(+), 11 deletions(-) diff --git a/compact/src/Compiler.ts b/compact/src/Compiler.ts index 53b292ae..e79bd48f 100755 --- a/compact/src/Compiler.ts +++ b/compact/src/Compiler.ts @@ -27,6 +27,12 @@ const COMPACTC_PATH: string = join(COMPACT_HOME, 'compactc'); * compiler.compile().catch(err => console.error(err)); * ``` * + * @example Compile specific directory + * ```typescript + * const compiler = new CompactCompiler('--skip-zk', 'security'); + * compiler.compile().catch(err => console.error(err)); + * ``` + * * @example Successful Compilation Output * ``` * ℹ [COMPILE] Found 2 .compact file(s) to compile @@ -48,19 +54,26 @@ const COMPACTC_PATH: string = join(COMPACT_HOME, 'compactc'); export class CompactCompiler { /** Stores the compiler flags passed via command-line arguments */ private readonly flags: string; + /** Optional target directory to limit compilation scope */ + private readonly targetDir?: string; /** * Constructs a new CompactCompiler instance, validating the `compactc` binary path. * * @param flags - Space-separated string of `compactc` flags (e.g., "--skip-zk --no-communications-commitment") + * @param targetDir - Optional subdirectory within src/ to limit compilation (e.g., "security", "utils") * @throws {Error} If the `compactc` binary is not found at the resolved path */ - constructor(flags: string) { + constructor(flags: string, targetDir?: string) { this.flags = flags.trim(); + this.targetDir = targetDir; const spinner = ora(); spinner.info(chalk.blue(`[COMPILE] COMPACT_HOME: ${COMPACT_HOME}`)); spinner.info(chalk.blue(`[COMPILE] COMPACTC_PATH: ${COMPACTC_PATH}`)); + if (this.targetDir) { + spinner.info(chalk.blue(`[COMPILE] TARGET_DIR: ${this.targetDir}`)); + } if (!existsSync(COMPACTC_PATH)) { spinner.fail( @@ -73,25 +86,36 @@ export class CompactCompiler { } /** - * Compiles all `.compact` files in the source directory and its subdirectories (e.g., `src/test/mock/`). - * Scans the `src` directory recursively for `.compact` files, compiles each one using `compactc`, - * and displays progress with a spinner and colored output. + * Compiles all `.compact` files in the source directory (or target subdirectory) and its subdirectories. + * Scans the `src` directory (or `src/{targetDir}`) recursively for `.compact` files, + * compiles each one using `compactc`, and displays progress with a spinner and colored output. * * @returns A promise that resolves when all files are compiled successfully * @throws {Error} If compilation fails for any file */ public async compile(): Promise { - const compactFiles: string[] = await this.getCompactFiles(SRC_DIR); + const searchDir = this.targetDir ? join(SRC_DIR, this.targetDir) : SRC_DIR; + + // Validate target directory exists + if (this.targetDir && !existsSync(searchDir)) { + const spinner = ora(); + spinner.fail(chalk.red(`[COMPILE] Error: Target directory ${searchDir} does not exist.`)); + throw new Error(`Target directory ${searchDir} does not exist`); + } + + const compactFiles: string[] = await this.getCompactFiles(searchDir); const spinner = ora(); if (compactFiles.length === 0) { - spinner.warn(chalk.yellow('[COMPILE] No .compact files found.')); + const searchLocation = this.targetDir ? `${this.targetDir}/` : ''; + spinner.warn(chalk.yellow(`[COMPILE] No .compact files found in ${searchLocation}.`)); return; } + const searchLocation = this.targetDir ? ` in ${this.targetDir}/` : ''; spinner.info( chalk.blue( - `[COMPILE] Found ${compactFiles.length} .compact file(s) to compile`, + `[COMPILE] Found ${compactFiles.length} .compact file(s) to compile${searchLocation}`, ), ); @@ -123,6 +147,7 @@ export class CompactCompiler { } if (entry.isFile() && fullPath.endsWith('.compact')) { + // Always return relative path from SRC_DIR, regardless of search directory return [relative(SRC_DIR, fullPath)]; } return []; diff --git a/compact/src/runCompiler.ts b/compact/src/runCompiler.ts index b7a84f24..9c6215ff 100644 --- a/compact/src/runCompiler.ts +++ b/compact/src/runCompiler.ts @@ -12,13 +12,20 @@ import { CompactCompiler } from './Compiler.js'; * ```bash * npx compact-compiler --skip-zk * ``` + * + * @example Compile specific directory + * ```bash + * npx compact-compiler --dir security --skip-zk + * ``` + * * Expected output: * ``` * ℹ [COMPILE] Compact compiler started * ℹ [COMPILE] COMPACT_HOME: /path/to/compactc * ℹ [COMPILE] COMPACTC_PATH: /path/to/compactc/compactc - * ℹ [COMPILE] Found 1 .compact file(s) to compile - * ✔ [COMPILE] [1/1] Compiled Foo.compact + * ℹ [COMPILE] TARGET_DIR: security + * ℹ [COMPILE] Found 1 .compact file(s) to compile in security/ + * ✔ [COMPILE] [1/1] Compiled security/AccessControl.compact * Compactc version: 0.24.0 * ``` */ @@ -26,8 +33,30 @@ async function runCompiler(): Promise { const spinner = ora(chalk.blue('[COMPILE] Compact Compiler started')).info(); try { - const compilerFlags = process.argv.slice(2).join(' '); - const compiler = new CompactCompiler(compilerFlags); + const args = process.argv.slice(2); + + // Parse arguments more robustly + let targetDir: string | undefined; + const compilerFlags: string[] = []; + + for (let i = 0; i < args.length; i++) { + if (args[i] === '--dir') { + if (i + 1 < args.length && !args[i + 1].startsWith('--')) { + targetDir = args[i + 1]; + i++; // Skip the next argument (directory name) + } else { + spinner.fail(chalk.red('[COMPILE] Error: --dir flag requires a directory name')); + console.log(chalk.yellow('Usage: compact-compiler --dir [other-flags]')); + console.log(chalk.yellow('Example: compact-compiler --dir security --skip-zk')); + process.exit(1); + } + } else { + // All other arguments are compiler flags + compilerFlags.push(args[i]); + } + } + + const compiler = new CompactCompiler(compilerFlags.join(' '), targetDir); await compiler.compile(); } catch (err) { spinner.fail( From 98b7a3210f04cb5f925fcc89b1259fda1a8c1a7f Mon Sep 17 00:00:00 2001 From: andrew Date: Sat, 16 Aug 2025 21:24:23 -0300 Subject: [PATCH 03/23] add granular compile scripts --- contracts/package.json | 5 ++++ turbo.json | 57 ++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 60 insertions(+), 2 deletions(-) diff --git a/contracts/package.json b/contracts/package.json index 493433f8..fc36646d 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -15,6 +15,11 @@ }, "scripts": { "compact": "compact-compiler", + "compact:access": "compact-compiler --dir access", + "compact:archive": "compact-compiler --dir archive", + "compact:security": "compact-compiler --dir security", + "compact:token": "compact-compiler --dir token", + "compact:utils": "compact-compiler --dir utils", "build": "compact-builder && tsc", "test": "vitest run", "types": "tsc -p tsconfig.json --noEmit", diff --git a/turbo.json b/turbo.json index fc4bb12f..4b5acd9c 100644 --- a/turbo.json +++ b/turbo.json @@ -1,10 +1,63 @@ { "$schema": "https://turbo.build/schema.json", "tasks": { - "compact": { + "compact:security": { "dependsOn": ["^build"], "env": ["COMPACT_HOME"], - "inputs": ["contracts/src/**/*.compact"], + "inputs": ["src/security/**/*.compact"], + "outputLogs": "new-only", + "outputs": ["artifacts/**/"] + }, + "compact:utils": { + "dependsOn": ["^build"], + "env": ["COMPACT_HOME"], + "inputs": ["src/utils/**/*.compact"], + "outputLogs": "new-only", + "outputs": ["artifacts/**/"] + }, + "compact:access": { + "dependsOn": ["^build", "compact:security", "compact:utils"], + "env": ["COMPACT_HOME"], + "inputs": [ + "src/access/**/*.compact", + "artifacts/**" + ], + "outputLogs": "new-only", + "outputs": ["artifacts/**/"] + }, + "compact:archive": { + "dependsOn": ["^build", "compact:utils"], + "env": ["COMPACT_HOME"], + "inputs": [ + "src/archive/**/*.compact", + "artifacts/**" + ], + "outputLogs": "new-only", + "outputs": ["artifacts/**/"] + }, + "compact:token": { + "dependsOn": ["^build", "compact:security", "compact:utils"], + "env": ["COMPACT_HOME"], + "inputs": [ + "src/token/**/*.compact", + "artifacts/**" + ], + "outputLogs": "new-only", + "outputs": ["artifacts/**/"] + }, + "compact": { + "dependsOn": [ + "compact:security", + "compact:utils", + "compact:access", + "compact:archive", + "compact:token" + ], + "env": ["COMPACT_HOME"], + "inputs": [ + "src/**/*.compact", + "test/**/*.compact" + ], "outputLogs": "new-only", "outputs": ["artifacts/**"] }, From ae9e3118b8df28b02d847e4aed97d7e68ecf8bf9 Mon Sep 17 00:00:00 2001 From: andrew Date: Sat, 16 Aug 2025 21:36:15 -0300 Subject: [PATCH 04/23] fix fmt --- compact/src/Compiler.ts | 10 ++++++++-- compact/src/runCompiler.ts | 14 +++++++++++--- turbo.json | 20 ++++---------------- 3 files changed, 23 insertions(+), 21 deletions(-) diff --git a/compact/src/Compiler.ts b/compact/src/Compiler.ts index e79bd48f..af426286 100755 --- a/compact/src/Compiler.ts +++ b/compact/src/Compiler.ts @@ -99,7 +99,11 @@ export class CompactCompiler { // Validate target directory exists if (this.targetDir && !existsSync(searchDir)) { const spinner = ora(); - spinner.fail(chalk.red(`[COMPILE] Error: Target directory ${searchDir} does not exist.`)); + spinner.fail( + chalk.red( + `[COMPILE] Error: Target directory ${searchDir} does not exist.`, + ), + ); throw new Error(`Target directory ${searchDir} does not exist`); } @@ -108,7 +112,9 @@ export class CompactCompiler { const spinner = ora(); if (compactFiles.length === 0) { const searchLocation = this.targetDir ? `${this.targetDir}/` : ''; - spinner.warn(chalk.yellow(`[COMPILE] No .compact files found in ${searchLocation}.`)); + spinner.warn( + chalk.yellow(`[COMPILE] No .compact files found in ${searchLocation}.`), + ); return; } diff --git a/compact/src/runCompiler.ts b/compact/src/runCompiler.ts index 9c6215ff..a1e9e002 100644 --- a/compact/src/runCompiler.ts +++ b/compact/src/runCompiler.ts @@ -45,9 +45,17 @@ async function runCompiler(): Promise { targetDir = args[i + 1]; i++; // Skip the next argument (directory name) } else { - spinner.fail(chalk.red('[COMPILE] Error: --dir flag requires a directory name')); - console.log(chalk.yellow('Usage: compact-compiler --dir [other-flags]')); - console.log(chalk.yellow('Example: compact-compiler --dir security --skip-zk')); + spinner.fail( + chalk.red('[COMPILE] Error: --dir flag requires a directory name'), + ); + console.log( + chalk.yellow( + 'Usage: compact-compiler --dir [other-flags]', + ), + ); + console.log( + chalk.yellow('Example: compact-compiler --dir security --skip-zk'), + ); process.exit(1); } } else { diff --git a/turbo.json b/turbo.json index 4b5acd9c..fdc0cb74 100644 --- a/turbo.json +++ b/turbo.json @@ -18,30 +18,21 @@ "compact:access": { "dependsOn": ["^build", "compact:security", "compact:utils"], "env": ["COMPACT_HOME"], - "inputs": [ - "src/access/**/*.compact", - "artifacts/**" - ], + "inputs": ["src/access/**/*.compact", "artifacts/**"], "outputLogs": "new-only", "outputs": ["artifacts/**/"] }, "compact:archive": { "dependsOn": ["^build", "compact:utils"], "env": ["COMPACT_HOME"], - "inputs": [ - "src/archive/**/*.compact", - "artifacts/**" - ], + "inputs": ["src/archive/**/*.compact", "artifacts/**"], "outputLogs": "new-only", "outputs": ["artifacts/**/"] }, "compact:token": { "dependsOn": ["^build", "compact:security", "compact:utils"], "env": ["COMPACT_HOME"], - "inputs": [ - "src/token/**/*.compact", - "artifacts/**" - ], + "inputs": ["src/token/**/*.compact", "artifacts/**"], "outputLogs": "new-only", "outputs": ["artifacts/**/"] }, @@ -54,10 +45,7 @@ "compact:token" ], "env": ["COMPACT_HOME"], - "inputs": [ - "src/**/*.compact", - "test/**/*.compact" - ], + "inputs": ["src/**/*.compact", "test/**/*.compact"], "outputLogs": "new-only", "outputs": ["artifacts/**"] }, From 4a9885ba116b7303b11a014e057e6cb3e69675d4 Mon Sep 17 00:00:00 2001 From: andrew Date: Sat, 16 Aug 2025 22:53:48 -0300 Subject: [PATCH 05/23] use fast compilation prior to tests, cache tests --- contracts/package.json | 2 +- turbo.json | 11 +++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/contracts/package.json b/contracts/package.json index fc36646d..acd41414 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -21,7 +21,7 @@ "compact:token": "compact-compiler --dir token", "compact:utils": "compact-compiler --dir utils", "build": "compact-builder && tsc", - "test": "vitest run", + "test": "compact-compiler --skip-zk && vitest run", "types": "tsc -p tsconfig.json --noEmit", "clean": "git clean -fXd" }, diff --git a/turbo.json b/turbo.json index fdc0cb74..86592d3e 100644 --- a/turbo.json +++ b/turbo.json @@ -50,9 +50,16 @@ "outputs": ["artifacts/**"] }, "test": { - "dependsOn": ["^build", "compact"], + "dependsOn": ["^build"], + "env": ["COMPACT_HOME"], + "inputs": [ + "src/**/*.ts", + "src/**/*.compact", + "vitest.config.ts", + "package.json" + ], "outputs": [], - "cache": false + "cache": true }, "build": { "dependsOn": ["^build"], From d707d2837d2ad247495bc68d5f00d0308170870e Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 18 Aug 2025 02:54:47 -0300 Subject: [PATCH 06/23] revert changes --- contracts/src/utils/Utils.compact | 46 ------------------- .../src/utils/test/mocks/MockUtils.compact | 16 ------- .../utils/test/simulators/UtilsSimulator.ts | 28 ----------- contracts/src/utils/test/utils.test.ts | 29 ------------ 4 files changed, 119 deletions(-) diff --git a/contracts/src/utils/Utils.compact b/contracts/src/utils/Utils.compact index b7291176..3528e10e 100644 --- a/contracts/src/utils/Utils.compact +++ b/contracts/src/utils/Utils.compact @@ -66,52 +66,6 @@ module Utils { return !keyOrAddress.is_left; } - /** - * @description Wraps a value as the `L` variant of a disjoint union (`Either`), - * following Compact conventions. - * - * Sets `is_left` to true, assigns the provided value to `left`, and sets `right` to `default` per convention. - * - * This helper is useful when you already have a value of type `L`, but the circuit interface expects an `Either`. - * It avoids manually constructing the full struct and ensures consistent formatting. - * - * @template L - Type of the Left variant. - * @template R - Type of the Right variant. - * - * @param {L} left - The value to wrap as the Left variant. - * @returns {Either} A disjoint union (`Either`) with the value in the `L` variant. - */ - export pure circuit wrapAsEitherLeft(left: L): Either { - return Either { - is_left: true, - left: left, - right: default - }; - } - - /** - * @description Wraps a value as the `R` variant of a disjoint union (`Either`), - * following Compact conventions. - * - * Sets `is_left` to false, assigns the provided value to `right`, and sets `left` to `default` per convention. - * - * This helper is useful when you already have a value of type `R`, but the circuit interface expects an `Either`. - * It avoids manually constructing the full struct and ensures consistent formatting. - * - * @template L - Type of the Left variant. - * @template R - Type of the Right variant. - * - * @param {R} right - The value to wrap as the `R` variant. - * @returns {Either} A disjoint union (`Either`) with the value in the `R` variant. - */ - export pure circuit wrapAsEitherRight(right: R): Either { - return Either { - is_left: false, - left: default, - right: right - }; - } - /** * @description A helper function that returns the empty string: "". * diff --git a/contracts/src/utils/test/mocks/MockUtils.compact b/contracts/src/utils/test/mocks/MockUtils.compact index c3b66d64..54fd45f3 100644 --- a/contracts/src/utils/test/mocks/MockUtils.compact +++ b/contracts/src/utils/test/mocks/MockUtils.compact @@ -25,22 +25,6 @@ export pure circuit isContractAddress(keyOrAddress: Either { - return Utils_wrapAsEitherLeft(pk); -} - -// Find a better way to test different combinations -// other than creating a circuit for each pair -export pure circuit wrapAsEitherPkOrAddressRight( - address: ContractAddress, -): Either { - return Utils_wrapAsEitherRight(address); -} - export pure circuit emptyString(): Opaque<"string"> { return Utils_emptyString(); } diff --git a/contracts/src/utils/test/simulators/UtilsSimulator.ts b/contracts/src/utils/test/simulators/UtilsSimulator.ts index 5f46223e..715569f8 100644 --- a/contracts/src/utils/test/simulators/UtilsSimulator.ts +++ b/contracts/src/utils/test/simulators/UtilsSimulator.ts @@ -139,34 +139,6 @@ export class UtilsSimulator ).result; } - /** - * @description Returns `pk` wrapped in an `Either` type. - * @param pk The target value to wrap. - * @returns `Either` with `pk` in the left position. - */ - public wrapAsEitherPkOrAddressLeft( - pk: ZswapCoinPublicKey, - ): Either { - return this.contract.circuits.wrapAsEitherPkOrAddressLeft( - this.circuitContext, - pk, - ).result; - } - - /** - * @description Returns `address` wrapped in an `Either` type. - * @param pk The target value to wrap. - * @returns `Either` with `address` in the right position. - */ - public wrapAsEitherPkOrAddressRight( - address: ContractAddress, - ): Either { - return this.contract.circuits.wrapAsEitherPkOrAddressRight( - this.circuitContext, - address, - ).result; - } - /** * @description A helper function that returns the empty string: "" * @returns The empty string: "" diff --git a/contracts/src/utils/test/utils.test.ts b/contracts/src/utils/test/utils.test.ts index 52b78acc..1398d4d9 100644 --- a/contracts/src/utils/test/utils.test.ts +++ b/contracts/src/utils/test/utils.test.ts @@ -1,9 +1,4 @@ import { describe, expect, it } from 'vitest'; -import type { - ContractAddress, - Either, - ZswapCoinPublicKey, -} from './../../../artifacts/MockUtils/contract/index.cjs'; // Combined imports import { UtilsSimulator } from './simulators/UtilsSimulator.js'; import * as contractUtils from './utils/address.js'; @@ -92,30 +87,6 @@ describe('Utils', () => { }); }); - describe('wrapAsEitherLeft', () => { - it('should wrap pk as left', () => { - const pk = contractUtils.encodeToPK('PK'); - const exp: Either = { - is_left: true, - left: pk, - right: { bytes: new Uint8Array(32).fill(0) }, - }; - expect(contract.wrapAsEitherPkOrAddressLeft(pk)).toEqual(exp); - }); - }); - - describe('wrapAsEitherRight', () => { - it('should wrap address as right', () => { - const address = contractUtils.encodeToPK('ADDRESS'); - const exp: Either = { - is_left: false, - left: { bytes: new Uint8Array(32).fill(0) }, - right: address, - }; - expect(contract.wrapAsEitherPkOrAddressRight(address)).toEqual(exp); - }); - }); - describe('emptyString', () => { it('should return the empty string', () => { expect(contract.emptyString()).toBe(EMPTY_STRING); From d11380220b2e38b1266629bc7a3e7c35e0194dba Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 18 Aug 2025 03:05:44 -0300 Subject: [PATCH 07/23] update readme with targeted compilation --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0e730124..bc8d31ab 100644 --- a/README.md +++ b/README.md @@ -88,10 +88,10 @@ Cached: 0 cached, 2 total Time: 7.178s ``` -**Note:** Speed up the development process by skipping the prover and verifier key file generation: +**Note:** Speed up the development process by targeting a single directory and skipping the prover and verifier key file generation: ```bash -turbo compact -- --skip-zk +turbo compact:token -- --skip-zk ``` ### Run tests From c004dd8a051b37f7a8dedfd0f4bf363936627075 Mon Sep 17 00:00:00 2001 From: andrew Date: Thu, 21 Aug 2025 12:49:28 -0300 Subject: [PATCH 08/23] build preserves dir structure --- compact/src/Builder.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/compact/src/Builder.ts b/compact/src/Builder.ts index b8f05197..55e90bf2 100755 --- a/compact/src/Builder.ts +++ b/compact/src/Builder.ts @@ -59,18 +59,21 @@ export class CompactBuilder { private readonly compilerFlags: string; private readonly steps: Array<{ cmd: string; msg: string; shell?: string }> = [ - { - cmd: 'tsc --project tsconfig.build.json', - msg: 'Compiling TypeScript', - }, { cmd: 'mkdir -p dist/artifacts && cp -Rf src/artifacts/* dist/artifacts/ 2>/dev/null || true', msg: 'Copying artifacts', shell: '/bin/bash', }, { - cmd: 'mkdir -p dist && find src -type f -name "*.compact" -exec cp {} dist/ \\; 2>/dev/null && rm dist/Mock*.compact 2>/dev/null || true', - msg: 'Copying and cleaning .compact files', + cmd: ` + # Copy .compact files preserving directory structure + find src -type f -name "*.compact" | while read file; do + rel_path="\${file#src/}" + mkdir -p "dist/\$(dirname "\$rel_path")" + cp "\$file" "dist/\$rel_path" + done + `, + msg: 'Copying .compact files (preserving structure)', shell: '/bin/bash', }, ]; From 6de7a17bfc26df09853e3f79186ddd349d3ee34d Mon Sep 17 00:00:00 2001 From: andrew Date: Sat, 23 Aug 2025 20:01:01 -0300 Subject: [PATCH 09/23] keep compact files in directories in dist, add tsc in build --- compact/src/Builder.ts | 96 +++++++++++++++++++++++------------------- contracts/package.json | 2 +- 2 files changed, 53 insertions(+), 45 deletions(-) diff --git a/compact/src/Builder.ts b/compact/src/Builder.ts index 55e90bf2..de4e2ab7 100755 --- a/compact/src/Builder.ts +++ b/compact/src/Builder.ts @@ -13,8 +13,9 @@ const execAsync = promisify(exec); /** * A class to handle the build process for a project. * Runs CompactCompiler as a prerequisite, then executes build steps (TypeScript compilation, - * artifact copying, etc.) - * with progress feedback and colored output for success and error states. + * artifact copying, etc.) with progress feedback and colored output for success and error states. + * + * Creates a clean distribution structure without src/ paths for professional import experience. * * @notice `cmd` scripts discard `stderr` output and fail silently because this is * handled in `executeStep`. @@ -32,51 +33,53 @@ const execAsync = promisify(exec); * Compactc version: 0.24.0 * ✔ [COMPILE] [2/2] Compiled MockAccessControl.compact * Compactc version: 0.24.0 - * ✔ [BUILD] [1/3] Compiling TypeScript - * ✔ [BUILD] [2/3] Copying artifacts - * ✔ [BUILD] [3/3] Copying and cleaning .compact files - * ``` - * - * @example Failed Compilation Output - * ``` - * ℹ [COMPILE] Found 2 .compact file(s) to compile - * ✖ [COMPILE] [1/2] Failed AccessControl.compact - * Compactc version: 0.24.0 - * Error: Expected ';' at line 5 in AccessControl.compact - * ``` - * - * @example Failed Build Step Output - * ``` - * ℹ [COMPILE] Found 2 .compact file(s) to compile - * ✔ [COMPILE] [1/2] Compiled AccessControl.compact - * ✔ [COMPILE] [2/2] Compiled MockAccessControl.compact - * ✖ [BUILD] [1/3] Failed Compiling TypeScript - * error TS1005: ';' expected at line 10 in file.ts - * [BUILD] ❌ Build failed: Command failed: tsc --project tsconfig.build.json + * ✔ [BUILD] [1/4] Cleaning dist directory + * ✔ [BUILD] [2/4] Compiling TypeScript + * ✔ [BUILD] [3/4] Copying .compact files + * ✔ [BUILD] [4/4] Copying package metadata * ``` */ export class CompactBuilder { private readonly compilerFlags: string; - private readonly steps: Array<{ cmd: string; msg: string; shell?: string }> = - [ - { - cmd: 'mkdir -p dist/artifacts && cp -Rf src/artifacts/* dist/artifacts/ 2>/dev/null || true', - msg: 'Copying artifacts', - shell: '/bin/bash', - }, - { - cmd: ` - # Copy .compact files preserving directory structure - find src -type f -name "*.compact" | while read file; do - rel_path="\${file#src/}" - mkdir -p "dist/\$(dirname "\$rel_path")" - cp "\$file" "dist/\$rel_path" - done - `, - msg: 'Copying .compact files (preserving structure)', - shell: '/bin/bash', - }, - ]; + private readonly steps: Array<{ cmd: string; msg: string; shell?: string }> = [ + // Step 1: Clean dist directory + { + cmd: 'rm -rf dist && mkdir -p dist', + msg: 'Cleaning dist directory', + shell: '/bin/bash', + }, + + // Step 2: TypeScript compilation (witnesses/ -> dist/witnesses/) + { + cmd: 'tsc --project tsconfig.build.json', + msg: 'Compiling TypeScript', + }, + + // Step 3: Copy .compact files preserving structure (excludes Mock* files and archive/) + { + cmd: ` + find src -type f -name "*.compact" ! -name "Mock*" ! -path "*/archive/*" | while read file; do + # Remove src/ prefix from path + rel_path="\${file#src/}" + mkdir -p "dist/\$(dirname "\$rel_path")" + cp "\$file" "dist/\$rel_path" + done + `, + msg: 'Copying .compact files (excluding mocks and archive)', + shell: '/bin/bash', + }, + + // Step 4: Copy essential files for distribution + { + cmd: ` + # Copy package.json and README + cp package.json dist/ 2>/dev/null || true + cp ../README.md dist/ # Go up one level to monorepo root + `, + msg: 'Copying package metadata', + shell: '/bin/bash', + }, + ]; /** * Constructs a new ProjectBuilder instance. @@ -102,6 +105,9 @@ export class CompactBuilder { for (const [index, step] of this.steps.entries()) { await this.executeStep(step, index, this.steps.length); } + + // Log completion + console.log(chalk.green('\n✅ Build complete!')); } /** @@ -158,6 +164,8 @@ export class CompactBuilder { .split('\n') .filter((line: string): boolean => line.trim() !== '') .map((line: string): string => ` ${line}`); - console.log(colorFn(lines.join('\n'))); + if (lines.length > 0) { + console.log(colorFn(lines.join('\n'))); + } } } diff --git a/contracts/package.json b/contracts/package.json index acd41414..1fdbf78a 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -20,7 +20,7 @@ "compact:security": "compact-compiler --dir security", "compact:token": "compact-compiler --dir token", "compact:utils": "compact-compiler --dir utils", - "build": "compact-builder && tsc", + "build": "compact-builder", "test": "compact-compiler --skip-zk && vitest run", "types": "tsc -p tsconfig.json --noEmit", "clean": "git clean -fXd" From 787f91bf476ad2e31b23028daa399ea1715e834d Mon Sep 17 00:00:00 2001 From: andrew Date: Sat, 23 Aug 2025 20:03:07 -0300 Subject: [PATCH 10/23] include explicit witnesses in tsconfig, remove sourceMap --- contracts/tsconfig.json | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/contracts/tsconfig.json b/contracts/tsconfig.json index 3e90b0a9..fc89bbac 100644 --- a/contracts/tsconfig.json +++ b/contracts/tsconfig.json @@ -1,5 +1,10 @@ { - "include": ["src/**/*.ts"], + "include": [ + "src/access/witnesses/**/*.ts", + "src/security/witnesses/**/*.ts", + "src/token/witnesses/**/*.ts", + "src/utils/witnesses/**/*.ts", + ], "compilerOptions": { "rootDir": "src", "outDir": "dist", @@ -13,7 +18,6 @@ "noImplicitAny": true, "strict": true, "isolatedModules": true, - "sourceMap": true, "resolveJsonModule": true, "esModuleInterop": true, "skipLibCheck": true From 865aa1101fd4fe040000172893d6cf964c97073e Mon Sep 17 00:00:00 2001 From: andrew Date: Sat, 23 Aug 2025 20:04:01 -0300 Subject: [PATCH 11/23] move compact compiler install to composite action --- .github/actions/setup/action.yml | 113 ++++++++++++++++++++++++++++++- .github/workflows/test.yml | 83 +---------------------- 2 files changed, 113 insertions(+), 83 deletions(-) diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml index 9cdc577c..d9a8565f 100644 --- a/.github/actions/setup/action.yml +++ b/.github/actions/setup/action.yml @@ -1,9 +1,29 @@ name: "Setup Environment" -description: "Sets up the environment with yarn, Node.js, and turbo" +description: "Sets up the environment with yarn, Node.js, turbo, and Compact compiler" + +inputs: + skip-compact: + description: "Skip Compact compiler installation" + required: false + default: "false" + +outputs: + compact-home: + description: "Path to Compact compiler installation" + value: ${{ steps.compact-outputs.outputs.compact-home }} + compact-version: + description: "Installed Compact compiler version" + value: ${{ steps.compact-outputs.outputs.version }} runs: using: "composite" steps: + - name: Set shared environment variables + shell: bash + run: | + echo "COMPILER_VERSION=0.24.0" >> $GITHUB_ENV + echo "LANGUAGE_VERSION=0.16.0" >> $GITHUB_ENV + - name: Get yarn cache directory path shell: bash id: yarn-cache-dir-path @@ -25,6 +45,14 @@ runs: restore-keys: | ${{ runner.os }}-turbo-${{ hashFiles('.turbo/*') }} + - name: Cache Compact compiler + if: inputs.skip-compact != 'true' + uses: actions/cache@v4 + id: compact-cache + with: + path: ~/compactc + key: compact-compiler-${{ env.COMPILER_VERSION }}-${{ runner.os }} + - name: Setup Node.js uses: actions/setup-node@v4 with: @@ -40,5 +68,86 @@ runs: env: TURBO_MAJOR_VERSION: 2 TURBO_TELEMETRY_DISABLED: 1 + run: npm install turbo@${{ env.TURBO_MAJOR_VERSION }} -g + + - name: Install Compact compiler + if: inputs.skip-compact != 'true' && steps.compact-cache.outputs.cache-hit != 'true' + shell: bash + run: | + set -euo pipefail + + COMPACT_HOME="$HOME/compactc" + COMPACT_ZIP_DIR="$HOME/compactc_download" + + echo "🔧 Installing Compact compiler v$COMPILER_VERSION..." + + mkdir -p "$COMPACT_HOME" + mkdir -p "$COMPACT_ZIP_DIR" + + ZIP_FILE="compactc_v${COMPILER_VERSION}_x86_64-unknown-linux-musl.zip" + DOWNLOAD_URL="https://d3fazakqrumx6p.cloudfront.net/artifacts/compiler/compactc_${COMPILER_VERSION}/${ZIP_FILE}" + + echo "⬇️ Downloading Compact compiler from $DOWNLOAD_URL..." + curl -fLs "$DOWNLOAD_URL" -o "$COMPACT_ZIP_DIR/compactc.zip" + + echo "🧪 Validating ZIP archive..." + if ! unzip -tq "$COMPACT_ZIP_DIR/compactc.zip"; then + echo "::error::❌ ZIP file is invalid or corrupted." + exit 1 + fi + + echo "📦 Extracting Compact compiler..." + unzip -q "$COMPACT_ZIP_DIR/compactc.zip" -d "$COMPACT_HOME" + chmod +x "$COMPACT_HOME"/{compactc,compactc.bin,zkir} + + echo "✅ Compact compiler extracted to $COMPACT_HOME" + + - name: Setup Compact environment + if: inputs.skip-compact != 'true' + shell: bash + run: | + COMPACT_HOME="$HOME/compactc" + echo "📁 Setting Compact environment variables..." + echo "COMPACT_HOME=$COMPACT_HOME" >> "$GITHUB_ENV" + echo "$COMPACT_HOME" >> "$GITHUB_PATH" + + if [ -f "$COMPACT_HOME/compactc" ]; then + echo "✅ Compact compiler is installed at $COMPACT_HOME" + else + echo "::error::❌ Compact compiler not found in $COMPACT_HOME" + exit 1 + fi + + - name: Set Compact outputs + if: inputs.skip-compact != 'true' + id: compact-outputs + shell: bash + run: | + echo "compact-home=$HOME/compactc" >> $GITHUB_OUTPUT + echo "version=$COMPILER_VERSION" >> $GITHUB_OUTPUT + + - name: Check compiler and language version + if: inputs.skip-compact != 'true' + shell: bash run: | - npm install turbo@${{ env.TURBO_MAJOR_VERSION }} -g + set -euo pipefail + + echo "🔍 Checking Compact compiler version..." + COMPILER_OUTPUT=$(compactc --version) + COMPUTED_COMPILER_VERSION=$(echo "$COMPILER_OUTPUT" | grep -oP '\b0\.[0-9]+\.[0-9]+\b' | head -n 1) + + if [ "$COMPUTED_COMPILER_VERSION" != "$COMPILER_VERSION" ]; then + echo "::error::❌ Compiler version mismatch!%0AExpected: $COMPILER_VERSION%0AGot: $COMPUTED_COMPILER_VERSION" + exit 1 + fi + echo "✅ Compiler version matches: $COMPUTED_COMPILER_VERSION" + + echo "🔍 Checking Compact language version..." + LANGUAGE_OUTPUT=$(compactc --language-version) + COMPUTED_LANGUAGE_VERSION=$(echo "$LANGUAGE_OUTPUT" | grep -oP '\b0\.[0-9]+\.[0-9]+\b' | tail -n 1) + + if [ "$COMPUTED_LANGUAGE_VERSION" != "$LANGUAGE_VERSION" ]; then + echo "::error::❌ Language version mismatch!%0AExpected: $LANGUAGE_VERSION%0AGot: $COMPUTED_LANGUAGE_VERSION" + exit 1 + fi + echo "✅ Language version matches: $COMPUTED_LANGUAGE_VERSION" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index bb523efd..17a35ff2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -6,11 +6,6 @@ on: branches: - main -env: - TURBO_TELEMETRY_DISABLED: 1 - COMPILER_VERSION: "0.24.0" - LANGUAGE_VERSION: "0.16.0" - jobs: run-suite: name: Run Test Suite @@ -26,82 +21,8 @@ jobs: - name: Setup Environment uses: ./.github/actions/setup - - name: Install Compact compiler - id: setup - shell: bash - run: | - set -euo pipefail - # Create directory for compiler - COMPACT_HOME="$HOME/compactc" - mkdir -p "$COMPACT_HOME" - - # Create URL - ZIP_FILE="compactc_v${COMPILER_VERSION}_x86_64-unknown-linux-musl.zip" - DOWNLOAD_URL="https://d3fazakqrumx6p.cloudfront.net/artifacts/compiler/compactc_${COMPILER_VERSION}/${ZIP_FILE}" - - echo "⬇️ Downloading Compact compiler..." - curl -Ls "$DOWNLOAD_URL" -o "$COMPACT_HOME/compactc.zip" - - echo "📦 Extracting..." - unzip -q "$COMPACT_HOME/compactc.zip" -d "$COMPACT_HOME" - chmod +x "$COMPACT_HOME"/{compactc,compactc.bin,zkir} - - echo "📁 Setting environment variables..." - echo "COMPACT_HOME=$COMPACT_HOME" >> "$GITHUB_ENV" - echo "$COMPACT_HOME" >> "$GITHUB_PATH" - - echo "✅ Verifying installation..." - if [ ! -f "$COMPACT_HOME/compactc" ]; then - echo "::error::❌ compactc not found in $COMPACT_HOME" - exit 1 - fi - - echo "🤖 Testing installation..." - "$COMPACT_HOME/compactc" --version - - - name: Check compiler and language version - run: | - COMPILER_OUTPUT=$(compactc --version) - COMPUTED_COMPILER_VERSION=$(echo "$COMPILER_OUTPUT" | grep -oP '\b0\.[0-9]+\.[0-9]+\b' | head -n 1) - if [ "$COMPUTED_COMPILER_VERSION" != "$COMPILER_VERSION" ]; then - errMsg="❌ Compiler version mismatch!%0AExpected: $COMPILER_VERSION%0AGot: $COMPUTED_COMPILER_VERSION" - echo "::error::$errMsg" - exit 1 - fi - echo "✅ Compiler version matches: $COMPUTED_COMPILER_VERSION" - - LANGUAGE_OUTPUT=$(compactc --language-version) - COMPUTED_LANGUAGE_VERSION=$(echo "$LANGUAGE_OUTPUT" | grep -oP '\b0\.[0-9]+\.[0-9]+\b' | tail -n 1) - if [ "$COMPUTED_LANGUAGE_VERSION" != "$LANGUAGE_VERSION" ]; then - errMsg="❌ Language version mismatch!%0AExpected: $LANGUAGE_VERSION%0AGot: $COMPUTED_LANGUAGE_VERSION" - echo "::error::$errMsg" - exit 1 - fi - - echo "✅ Language version matches: $COMPUTED_LANGUAGE_VERSION" - - - name: Compile contracts (with retry on hash mismatch) - shell: bash - run: | - set -euo pipefail - - compile() { - echo "⚙️ Running Compact compilation..." - if ! output=$(turbo compact --concurrency=1 2>&1); then - echo "❌ Compilation failed." - if echo "$output" | grep -q "Hash mismatch" && [ -d "$HOME/.cache/midnight/zk-params" ]; then - echo "⚠️ Hash mismatch detected *and* zk-params exists. Removing cache..." - rm -rf "$HOME/.cache/midnight/zk-params" - echo "::notice::♻️ Retrying compilation after clearing zk-params..." - turbo compact --concurrency=1 || { echo "::error::❌ Retry also failed."; exit 1; } - else - echo "🚫 Compilation failed for another reason or zk-params missing. No retry." - exit 1 - fi - fi - } - - compile + - name: Compile contracts (with retry) + run: turbo compact --concurrency=1 - name: Run type checks run: turbo types From 579ef4de2c8012d216dcef97f2dc87835c53e8d3 Mon Sep 17 00:00:00 2001 From: andrew Date: Sat, 23 Aug 2025 20:05:34 -0300 Subject: [PATCH 12/23] add prepare-release workflow (version bump) --- .github/workflows/prepare-release.yml | 57 +++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 .github/workflows/prepare-release.yml diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml new file mode 100644 index 00000000..2fa28eff --- /dev/null +++ b/.github/workflows/prepare-release.yml @@ -0,0 +1,57 @@ +name: Update version on new release branch + +on: + create: + +permissions: + contents: write + pull-requests: write + +jobs: + update_version: + if: github.ref_type == 'branch' && startsWith(github.ref, 'refs/heads/release-v') + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v5 + + - name: Extract current version + run: | + CURRENT_VERSION=$(node -p "require('./contracts/package.json').version") + echo "CURRENT_VERSION=$CURRENT_VERSION" >> "$GITHUB_ENV" + + - name: Extract new version number + run: echo "NEW_VERSION=${GITHUB_REF#refs/heads/release-v}" >> "$GITHUB_ENV" + + - name: Replace version in files + run: | + echo "Current version: $CURRENT_VERSION" + echo "New version: $NEW_VERSION" + + # Update package.json version field manually + cd contracts + node -e " + const fs = require('fs'); + const pkg = require('./package.json'); + pkg.version = '$NEW_VERSION'; + fs.writeFileSync('./package.json', JSON.stringify(pkg, null, 2) + '\n'); + console.log('Updated package.json to version $NEW_VERSION'); + " + # Update yarn.lock to reflect the new version + yarn install + cd .. + + # Escape special regex characters in version strings + ESCAPED_CURRENT=$(echo "$CURRENT_VERSION" | sed 's/[.[\*^$()+?{|]/\\&/g') + ESCAPED_NEW=$(echo "$NEW_VERSION" | sed 's/[.[\*^$()+?{|]/\\&/g') + find . -type f \ + -not -path '*/\.*' \ + -not -path './docs/package-lock.json' \ + -not -path './RELEASING.md' \ + -exec sed -i "s/$ESCAPED_CURRENT/$ESCAPED_NEW/g" {} + 2>/dev/null || true + + - name: Auto-commit changes + uses: stefanzweifel/git-auto-commit-action@778341af668090896ca464160c2def5d1d1a3eb0 #v6.0.1 + with: + commit_message: Bump version to ${{ env.NEW_VERSION }} From 7f64555aff2d0e7629516b06b5aee08f1f7f3624 Mon Sep 17 00:00:00 2001 From: andrew Date: Sat, 23 Aug 2025 20:05:51 -0300 Subject: [PATCH 13/23] add release workflow --- .github/workflows/release.yml | 101 ++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..7fd6b42b --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,101 @@ +name: Publish Package on Release + +on: + release: + types: [published] + +env: + COMPILER_VERSION: "0.24.0" + +jobs: + publish: + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Environment + uses: ./.github/actions/setup + + - name: Compile contracts (with retry) + run: | + # Your retry logic - Compact is already in PATH + turbo compact --concurrency=1 + + - name: Validate version consistency + run: | + RELEASE_VERSION=${GITHUB_REF#refs/tags/v} + PACKAGE_VERSION=$(node -p "require('./contracts/package.json').version") + if [ "$RELEASE_VERSION" != "$PACKAGE_VERSION" ]; then + echo "❌ Version mismatch: Release $RELEASE_VERSION vs Package $PACKAGE_VERSION" + exit 1 + fi + echo "✅ Version consistency validated: $RELEASE_VERSION" + + - name: Setup npm registry + uses: actions/setup-node@v4 + with: + registry-url: 'https://registry.npmjs.org' + + - name: Build + run: turbo build + + - name: Pack tarball + id: pack + run: | + cd contracts/dist + TARBALL=$(npm pack | tail -1) + echo "tarball_name=$TARBALL" >> $GITHUB_OUTPUT + echo "tarball=$(pwd)/$TARBALL" >> $GITHUB_OUTPUT + + # Determine dist-tag based on semver prerelease + PACKAGE_VERSION=$(node -p "require('./package.json').version") + if [[ "$PACKAGE_VERSION" =~ -.*$ ]]; then + # Has prerelease suffix (anything after -) + if [[ "$PACKAGE_VERSION" =~ -(alpha|beta|rc) ]]; then + echo "tag=beta" >> $GITHUB_OUTPUT + else + echo "tag=next" >> $GITHUB_OUTPUT + fi + else + # Stable release + echo "tag=latest" >> $GITHUB_OUTPUT + fi + + - name: Verify tarball integrity + run: | + echo "=== Verifying tarball contents ===" + PACKAGE_NAME=$(tar xfO "${{ steps.pack.outputs.tarball }}" package/package.json | jq -r .name) + PACKAGE_VERSION=$(tar xfO "${{ steps.pack.outputs.tarball }}" package/package.json | jq -r .version) + PRIVATE_FIELD=$(tar xfO "${{ steps.pack.outputs.tarball }}" package/package.json | jq -r '.private // "not found"') + + echo "📦 Package: $PACKAGE_NAME@$PACKAGE_VERSION" + echo "🏷️ Tag: ${{ steps.pack.outputs.tag }}" + echo "🔒 Private field: $PRIVATE_FIELD" + + # Ensure no private field + if [ "$PRIVATE_FIELD" = "true" ]; then + echo "❌ Tarball contains private: true - cannot publish" + exit 1 + fi + + - name: Publish to npm + run: | + # Create .npmrc with auth token + echo "//registry.npmjs.org/:_authToken=\${NPM_TOKEN}" > .npmrc + + # Publish the tarball with appropriate tag + npm publish "${{ steps.pack.outputs.tarball }}" --tag "${{ steps.pack.outputs.tag }}" --access public + env: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + + - name: Log success + run: | + PACKAGE_NAME=$(tar xfO "${{ steps.pack.outputs.tarball }}" package/package.json | jq -r .name) + PACKAGE_VERSION=$(tar xfO "${{ steps.pack.outputs.tarball }}" package/package.json | jq -r .version) + echo "✅ Successfully published $PACKAGE_NAME@$PACKAGE_VERSION to npm with tag ${{ steps.pack.outputs.tag }}" + echo "📦 Install with: npm install $PACKAGE_NAME@${{ steps.pack.outputs.tag }}" From f2067a8e7698e523799343420d56129e2b9d8fb3 Mon Sep 17 00:00:00 2001 From: andrew Date: Sat, 23 Aug 2025 20:17:01 -0300 Subject: [PATCH 14/23] add releasing doc --- RELEASING.md | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 RELEASING.md diff --git a/RELEASING.md b/RELEASING.md new file mode 100644 index 00000000..6ad36f80 --- /dev/null +++ b/RELEASING.md @@ -0,0 +1,42 @@ +# Releasing + +(1) Checkout the branch to be released. +This will usually be `main` except in the event of a hotfix. +For hotfixes, checkout the release branch you want to fix. + +(2) Create a new release branch. + +```sh +git checkout -b release-v0.2.0 +``` + +(3) Push and open a PR targeting `main` to carefully review the release changes. +This will trigger a GitHub workflow that automatically bumps the version number throughout the project. + +```sh +git push release-v0.2.0 +``` + +(4) Once merged, pull the changes from the release branch. +Then, create a tag on the release branch and push it to the main repository. +Note that the version changes must be pulled *before* the tag is created; +otherwise, the version validation check will fail in the release workflow. + +```sh +git pull +git tag v0.2.0 +git push origin v0.2.0 +``` + +(5) After that, go to the repo's [releases page](https://github.com/OpenZeppelin/compact-contracts/releases/). +[Create a new release](https://github.com/OpenZeppelin/compact-contracts/releases/new) with the new tag and the base branch as target (`main` except in the event of a hotfix). +Make sure to write a detailed release description and a short changelog. +Once published, this will trigger a workflow to upload the release tarball to npm. + +(6) Finally, from the released tag, +create and push a doc branch to deploy the corresponding version to the doc-site. + +```sh +git checkout -b docs-v0.2.0 +git push docs-v0.2.0 +``` From f6c7c5cdb5336fd88572ccb28f80cb20102466ad Mon Sep 17 00:00:00 2001 From: andrew Date: Sat, 23 Aug 2025 20:24:33 -0300 Subject: [PATCH 15/23] remove comment --- .github/workflows/release.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7fd6b42b..f62cbc69 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -23,7 +23,6 @@ jobs: - name: Compile contracts (with retry) run: | - # Your retry logic - Compact is already in PATH turbo compact --concurrency=1 - name: Validate version consistency From d46ffaf2fc3766045d78ff593252e4bef3b2fe6a Mon Sep 17 00:00:00 2001 From: andrew Date: Sat, 23 Aug 2025 20:32:22 -0300 Subject: [PATCH 16/23] remove unused env, remove redundant compile in favor of build --- .github/workflows/release.yml | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f62cbc69..86c8a32e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,9 +4,6 @@ on: release: types: [published] -env: - COMPILER_VERSION: "0.24.0" - jobs: publish: runs-on: ubuntu-latest @@ -21,9 +18,8 @@ jobs: - name: Setup Environment uses: ./.github/actions/setup - - name: Compile contracts (with retry) - run: | - turbo compact --concurrency=1 + - name: Build contracts + run: turbo build - name: Validate version consistency run: | @@ -40,9 +36,6 @@ jobs: with: registry-url: 'https://registry.npmjs.org' - - name: Build - run: turbo build - - name: Pack tarball id: pack run: | From f71f6160bf77de92fdfad6d5ec19d0a63c50cbc8 Mon Sep 17 00:00:00 2001 From: andrew Date: Sat, 23 Aug 2025 20:46:07 -0300 Subject: [PATCH 17/23] add provenance --- .github/workflows/release.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 86c8a32e..132ab34b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -84,6 +84,7 @@ jobs: npm publish "${{ steps.pack.outputs.tarball }}" --tag "${{ steps.pack.outputs.tag }}" --access public env: NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + NPM_CONFIG_PROVENANCE: true - name: Log success run: | From 9f9be1edd6ad2b01e027934d4197f217ee9b1d5e Mon Sep 17 00:00:00 2001 From: andrew Date: Sat, 23 Aug 2025 21:17:45 -0300 Subject: [PATCH 18/23] remove private field from contracts --- contracts/package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/contracts/package.json b/contracts/package.json index 1fdbf78a..b18995b6 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -1,6 +1,5 @@ { "name": "@openzeppelin-compact/contracts", - "private": true, "type": "module", "main": "dist/index.js", "module": "dist/index.js", From 60b5b3adfc6b7c73c4c307d9ae1cc62a0caa635f Mon Sep 17 00:00:00 2001 From: andrew Date: Sat, 23 Aug 2025 21:28:10 -0300 Subject: [PATCH 19/23] add manifest metadata, remove private field --- contracts/package.json | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/contracts/package.json b/contracts/package.json index b18995b6..6290b9f4 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -1,17 +1,25 @@ { "name": "@openzeppelin-compact/contracts", - "type": "module", - "main": "dist/index.js", - "module": "dist/index.js", - "types": "./dist/index.d.ts", - "exports": { - ".": { - "types": "./dist/index.d.ts", - "require": "./dist/index.js", - "import": "./dist/index.js", - "default": "./dist/index.js" - } + "version": "0.0.1", + "description": "OpenZeppelin Compact contract library", + "keywords": [ + "openzeppelin", + "compact", + "midnight", + "contracts", + "security" + ], + "repository": { + "type": "git", + "url": "https://github.com/OpenZeppelin/compact-contracts.git" + }, + "license": "MIT", + "author": "OpenZeppelin Community ", + "bugs": { + "url": "https://github.com/OpenZeppelin/compact-contracts/issues" }, + "homepage": "https://docs.openzeppelin.com/contracts-compact/", + "type": "module", "scripts": { "compact": "compact-compiler", "compact:access": "compact-compiler --dir access", From 67c95ff4d3118089795f779565be0a4925c37719 Mon Sep 17 00:00:00 2001 From: andrew Date: Sat, 23 Aug 2025 21:29:37 -0300 Subject: [PATCH 20/23] fix fmt --- compact/src/Builder.ts | 54 +++++++++++++++++++++--------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/compact/src/Builder.ts b/compact/src/Builder.ts index de4e2ab7..3c4d2c78 100755 --- a/compact/src/Builder.ts +++ b/compact/src/Builder.ts @@ -14,7 +14,7 @@ const execAsync = promisify(exec); * A class to handle the build process for a project. * Runs CompactCompiler as a prerequisite, then executes build steps (TypeScript compilation, * artifact copying, etc.) with progress feedback and colored output for success and error states. - * + * * Creates a clean distribution structure without src/ paths for professional import experience. * * @notice `cmd` scripts discard `stderr` output and fail silently because this is @@ -41,23 +41,24 @@ const execAsync = promisify(exec); */ export class CompactBuilder { private readonly compilerFlags: string; - private readonly steps: Array<{ cmd: string; msg: string; shell?: string }> = [ - // Step 1: Clean dist directory - { - cmd: 'rm -rf dist && mkdir -p dist', - msg: 'Cleaning dist directory', - shell: '/bin/bash', - }, + private readonly steps: Array<{ cmd: string; msg: string; shell?: string }> = + [ + // Step 1: Clean dist directory + { + cmd: 'rm -rf dist && mkdir -p dist', + msg: 'Cleaning dist directory', + shell: '/bin/bash', + }, - // Step 2: TypeScript compilation (witnesses/ -> dist/witnesses/) - { - cmd: 'tsc --project tsconfig.build.json', - msg: 'Compiling TypeScript', - }, + // Step 2: TypeScript compilation (witnesses/ -> dist/witnesses/) + { + cmd: 'tsc --project tsconfig.build.json', + msg: 'Compiling TypeScript', + }, - // Step 3: Copy .compact files preserving structure (excludes Mock* files and archive/) - { - cmd: ` + // Step 3: Copy .compact files preserving structure (excludes Mock* files and archive/) + { + cmd: ` find src -type f -name "*.compact" ! -name "Mock*" ! -path "*/archive/*" | while read file; do # Remove src/ prefix from path rel_path="\${file#src/}" @@ -65,21 +66,21 @@ export class CompactBuilder { cp "\$file" "dist/\$rel_path" done `, - msg: 'Copying .compact files (excluding mocks and archive)', - shell: '/bin/bash', - }, + msg: 'Copying .compact files (excluding mocks and archive)', + shell: '/bin/bash', + }, - // Step 4: Copy essential files for distribution - { - cmd: ` + // Step 4: Copy essential files for distribution + { + cmd: ` # Copy package.json and README cp package.json dist/ 2>/dev/null || true cp ../README.md dist/ # Go up one level to monorepo root `, - msg: 'Copying package metadata', - shell: '/bin/bash', - }, - ]; + msg: 'Copying package metadata', + shell: '/bin/bash', + }, + ]; /** * Constructs a new ProjectBuilder instance. @@ -106,7 +107,6 @@ export class CompactBuilder { await this.executeStep(step, index, this.steps.length); } - // Log completion console.log(chalk.green('\n✅ Build complete!')); } From 9982d953105bd03cf66e4ee0be9f24ecc10f8f75 Mon Sep 17 00:00:00 2001 From: andrew Date: Sun, 24 Aug 2025 02:27:33 -0300 Subject: [PATCH 21/23] improve find pattern --- .github/workflows/prepare-release.yml | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml index 2fa28eff..e0d88a52 100644 --- a/.github/workflows/prepare-release.yml +++ b/.github/workflows/prepare-release.yml @@ -42,14 +42,20 @@ jobs: yarn install cd .. - # Escape special regex characters in version strings - ESCAPED_CURRENT=$(echo "$CURRENT_VERSION" | sed 's/[.[\*^$()+?{|]/\\&/g') - ESCAPED_NEW=$(echo "$NEW_VERSION" | sed 's/[.[\*^$()+?{|]/\\&/g') - find . -type f \ + # Escape special characters for sed + ESCAPED_CURRENT=$(printf '%s' "$CURRENT_VERSION" | sed -e 's/[\/&]/\\&/g') + ESCAPED_NEW=$(printf '%s' "$NEW_VERSION" | sed -e 's/[\/&]/\\&/g') + + # Replace version in contracts/src/ + find ./contracts/src/ -type f \ + -not -path '*/\.*' \ + -exec sed -i "s#$ESCAPED_CURRENT#$ESCAPED_NEW#g" {} + + + # Replace version in docs/, excluding package-lock.json + find ./docs/ -type f \ -not -path '*/\.*' \ -not -path './docs/package-lock.json' \ - -not -path './RELEASING.md' \ - -exec sed -i "s/$ESCAPED_CURRENT/$ESCAPED_NEW/g" {} + 2>/dev/null || true + -exec sed -i "s#$ESCAPED_CURRENT#$ESCAPED_NEW#g" {} + - name: Auto-commit changes uses: stefanzweifel/git-auto-commit-action@778341af668090896ca464160c2def5d1d1a3eb0 #v6.0.1 From 5cb630012fe04d43004b6626f3e1a7802718e045 Mon Sep 17 00:00:00 2001 From: andrew Date: Sun, 24 Aug 2025 02:44:33 -0300 Subject: [PATCH 22/23] further improve find pattern --- .github/workflows/prepare-release.yml | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml index e0d88a52..82b1cc93 100644 --- a/.github/workflows/prepare-release.yml +++ b/.github/workflows/prepare-release.yml @@ -47,15 +47,12 @@ jobs: ESCAPED_NEW=$(printf '%s' "$NEW_VERSION" | sed -e 's/[\/&]/\\&/g') # Replace version in contracts/src/ - find ./contracts/src/ -type f \ - -not -path '*/\.*' \ - -exec sed -i "s#$ESCAPED_CURRENT#$ESCAPED_NEW#g" {} + + find ./contracts/src/ -type d -name '.*' -prune -o \ + -type f -exec sed -i "s#$ESCAPED_CURRENT#$ESCAPED_NEW#g" {} + # Replace version in docs/, excluding package-lock.json - find ./docs/ -type f \ - -not -path '*/\.*' \ - -not -path './docs/package-lock.json' \ - -exec sed -i "s#$ESCAPED_CURRENT#$ESCAPED_NEW#g" {} + + find ./docs/ -type d -name '.*' -prune -o \ + -type f ! -name 'package-lock.json' -exec sed -i "s#$ESCAPED_CURRENT#$ESCAPED_NEW#g" {} + - name: Auto-commit changes uses: stefanzweifel/git-auto-commit-action@778341af668090896ca464160c2def5d1d1a3eb0 #v6.0.1 From 90f876232088ee45f86fccd3e564a7fa322cd84f Mon Sep 17 00:00:00 2001 From: Andrew Fleming Date: Fri, 29 Aug 2025 11:26:04 -0500 Subject: [PATCH 23/23] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: ⟣ €₥ℵ∪ℓ ⟢ <34749913+emnul@users.noreply.github.com> Signed-off-by: Andrew Fleming --- .github/workflows/release.yml | 4 ++-- .github/workflows/test.yml | 2 +- contracts/tsconfig.json | 6 ++---- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 132ab34b..7a57be73 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,13 +13,13 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Setup Environment uses: ./.github/actions/setup - name: Build contracts - run: turbo build + run: turbo build --filter=!'docs' - name: Validate version consistency run: | diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 17a35ff2..a21f657b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,7 +22,7 @@ jobs: uses: ./.github/actions/setup - name: Compile contracts (with retry) - run: turbo compact --concurrency=1 + run: turbo compact --filter=@openzeppelin-compact/contracts --concurrency=1 - name: Run type checks run: turbo types diff --git a/contracts/tsconfig.json b/contracts/tsconfig.json index fc89bbac..04859e06 100644 --- a/contracts/tsconfig.json +++ b/contracts/tsconfig.json @@ -1,10 +1,8 @@ { "include": [ - "src/access/witnesses/**/*.ts", - "src/security/witnesses/**/*.ts", - "src/token/witnesses/**/*.ts", - "src/utils/witnesses/**/*.ts", + "src/**/witnesses/**/*.ts" ], + "exclude": ["src/archive/"], "compilerOptions": { "rootDir": "src", "outDir": "dist",