testing

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese
Scope This skill covers the two error layers in Compact (static vs dynamic), how to read compiler messages (especially undeclared disclosure traces), the fast development loop with
--skip-zk
, version management across 6 components, and the most common debugging patterns. Official testing docs are "coming soon" — this is battle-tested from real development.
适用范围 本技能涵盖Compact中的两层错误(静态与动态)、如何解读编译器消息(尤其是未声明披露追踪信息)、使用
--skip-zk
的快速开发循环、6个组件的版本管理,以及最常见的调试模式。官方测试文档“即将推出”——本内容来自实际开发中的实战经验。

1) Two Error Layers

1) 两层错误类型

Compact has two error types, not one:
Static errors — Caught by the compiler before generating any output. You see these in your terminal while developing. Static errors are your friend — the compiler tells you exactly what's wrong and where.
Dynamic errors — Caught at runtime by the generated JavaScript. You see these when a circuit executes. These are harder to debug because they happen inside generated code you didn't write.
Compact包含两种错误类型,而非一种:
静态错误 —— 在生成任何输出前由编译器捕获。开发时会在终端中看到这些错误。静态错误是你的帮手——编译器会准确告诉你问题所在及位置。
动态错误 —— 由生成的JavaScript在运行时捕获。电路执行时会看到这些错误。这类错误更难调试,因为它们发生在你未编写的生成代码内部。

Static Errors (Compile Time)

静态错误(编译阶段)

Error typeWhat it isWhen caught
SyntaxMalformed codeParser
Type mismatchWrong type usedType checker
Undeclared disclosure
disclose()
missing
Witness protection
Undefined referenceUnknown identifierName resolver
Generic not specializedGeneric entity used at top levelScope checker
Recursive structStruct refers to itselfDeclaration checker
Recursive circuitCircuit calls itselfDeclaration checker
return
in
for
return
inside loop
Statement checker
Sealed ledger writeWrite to sealed field in circuitDeclaration checker
If the compiler produces no output, there's at least one static error. Check the messages.
错误类型说明捕获时机
语法错误代码格式错误解析阶段
类型不匹配使用了错误的类型类型检查阶段
未声明披露缺少
disclose()
见证保护阶段
未定义引用未知标识符名称解析阶段
泛型未特化顶层使用了泛型实体作用域检查阶段
递归结构体结构体引用自身声明检查阶段
递归电路电路调用自身声明检查阶段
for
循环内使用
return
循环内部包含
return
语句
语句检查阶段
密封账本写入向电路中的密封字段写入数据声明检查阶段
如果编译器未生成任何输出,则至少存在一个静态错误。请检查提示消息。

Dynamic Errors (Runtime)

动态错误(运行阶段)

Error typeWhat it isExample
Type mismatchWrong argument type/numberCalling with wrong args
OverflowCast value too large for target
1000 as Uint<8>
UnderflowCounter decremented below zero
counter -= 1
when at 0
Uninitialized nested valueNested ledger state not initialized
map.lookup(k).lookup(k2)
before insert
Merkle tree fullInsert into full tree
tree.insert()
when
isFull()
Dynamic errors are harder to debug. Read the error message for the line number in your source file.
错误类型说明示例
类型不匹配参数类型/数量错误使用错误参数调用函数
溢出转换值超出目标类型范围
1000 as Uint<8>
下溢计数器递减至零以下计数器为0时执行
counter -= 1
未初始化嵌套值嵌套账本状态未初始化插入前执行
map.lookup(k).lookup(k2)
默克尔树已满向已满的树中插入数据树已满时执行
tree.insert()
动态错误更难调试。请查看错误消息中指向的源文件行号。

2) Reading Compiler Error Messages

2) 解读编译器错误消息

Type Error

类型错误

/path/contract.compact line 12 char 5:
  type error: expected Uint<64>, got Field
Read: line:character, expected type, got type. The caret (
^
) points to the problem token.
/path/contract.compact line 12 char 5:
  type error: expected Uint<64>, got Field
解读方式:行号:字符位置,预期类型,实际类型。脱字符(
^
)指向问题标记。

Undeclared Disclosure

未声明披露

Exception: /path/contract.compact line 6 char 11:
  potential witness-value disclosure must be declared but is not:
    witness value potentially disclosed:
      the return value of witness getBalance at line 2 char 1
    nature of the disclosure:
      ledger operation might disclose the witness value
    via this path through the program:
      the right-hand side of = at line 6 char 11
Read this bottom to top. The path traces how witness data traveled:
  1. Origin:
    getBalance()
    at line 2
  2. Path: flows through the right-hand side of the assignment
  3. Destination: the ledger operation at line 6
Fix: Add
disclose()
somewhere along that path, as close to the disclosure point as possible.
Exception: /path/contract.compact line 6 char 11:
  potential witness-value disclosure must be declared but is not:
    witness value potentially disclosed:
      the return value of witness getBalance at line 2 char 1
    nature of the disclosure:
      ledger operation might disclose the witness value
    via this path through the program:
      the right-hand side of = at line 6 char 11
从下往上阅读。路径追踪了见证数据的传播流程:
  1. 源头: 第2行的
    getBalance()
  2. 路径: 流经赋值语句的右侧
  3. 终点: 第6行的账本操作
修复方法: 在该路径上添加
disclose()
,尽可能靠近披露点。

Missing
disclose()
on Return

返回值缺少
disclose()

Exception: line 5 char 3:
  potential witness-value disclosure must be declared but is not:
    witness value potentially disclosed:
      the return value of witness getBalance at line 2 char 1
    nature of the disclosure:
      the value returned from exported circuit check might disclose
      the result of a comparison involving the witness value
Even a
Boolean
comparison result counts as disclosure. Wrap the witness call or the return value with
disclose()
.
Exception: line 5 char 3:
  potential witness-value disclosure must be declared but is not:
    witness value potentially disclosed:
      the return value of witness getBalance at line 2 char 1
    nature of the disclosure:
      the value returned from exported circuit check might disclose
      the result of a comparison involving the witness value
即使是
Boolean
比较结果也属于披露范畴。将见证调用或返回值用
disclose()
包裹。

Version Mismatch

版本不匹配

Error: runtime version mismatch: expected 0.15.0, got 0.14.2
The compiled contract expects a different runtime version. See version management below.
Error: runtime version mismatch: expected 0.15.0, got 0.14.2
编译后的合约需要不同的运行时版本。请查看下方的版本管理部分。

3) The
--skip-zk
Development Loop

3) 使用
--skip-zk
的开发循环

Generating proving keys is slow. During iterative development, skip it:
bash
compact compile --skip-zk contracts/contract.compact contracts/managed/contract
This produces
contract/index.js
and
compiler/contract-info.json
, enough to test logic. Re-enable for final builds.
生成证明密钥的过程很慢。在迭代开发期间,可以跳过此步骤:
bash
compact compile --skip-zk contracts/contract.compact contracts/managed/contract
此命令会生成
contract/index.js
和
compiler/contract-info.json
,足以测试逻辑。最终构建时需重新启用该步骤。

4) Version Management

4) 版本管理

Midnight has 6 components that must stay in sync. When they're not, you get opaque runtime errors about version mismatches.
ComponentWhat it isHow to check
CLI tool
compact
binary
compact --version
Compiler
compactc
compact compile --version
Runtime
@midnight-ntwrk/compact-runtime
npm list
Ledger
@midnight-ntwrk/ledger-v8
npm list
JS libraries
@midnight-ntwrk/midnight-js-*
npm list
Proof serverDocker imageimage tag
Midnight有6个组件必须保持版本同步。如果版本不一致,会出现关于版本不匹配的模糊运行时错误。
组件说明检查方式
CLI工具
compact
二进制文件
compact --version
编译器
compactc
compact compile --version
运行时
@midnight-ntwrk/compact-runtime
npm list
账本
@midnight-ntwrk/ledger-v8
npm list
JS库
@midnight-ntwrk/midnight-js-*
npm list
证明服务器Docker镜像镜像标签

Check Current Versions

检查当前版本

bash
compact --version
compact compile --version
npm list @midnight-ntwrk/compact-runtime
npm list @midnight-ntwrk/ledger-v8
bash
compact --version
compact compile --version
npm list @midnight-ntwrk/compact-runtime
npm list @midnight-ntwrk/ledger-v8

Consult the Compatibility Matrix

参考兼容性矩阵

The official release compatibility matrix is the source of truth. Never mix versions without checking it.
官方发布兼容性矩阵是权威参考。未经检查切勿混合使用不同版本。

Lock Exact Versions in
package.json

在
package.json
中锁定精确版本

json
{
  "dependencies": {
    "@midnight-ntwrk/compact-runtime": "0.15.0",
    "@midnight-ntwrk/ledger-v8": "8.0.3"
  }
}
Do not use
^
or
~
— these allow automatic updates that silently break compatibility.
json
{
  "dependencies": {
    "@midnight-ntwrk/compact-runtime": "0.15.0",
    "@midnight-ntwrk/ledger-v8": "8.0.3"
  }
}
请勿使用
^
或
~
——这些符号会允许自动更新,从而悄无声息地破坏兼容性。

Use
npm ci
for Reproducible Installs

使用
npm ci
进行可复现安装

bash
rm -rf node_modules
npm ci
npm ci
installs exactly what's in
package-lock.json
.
npm install
fetches the latest matching version.
bash
rm -rf node_modules
npm ci
npm ci
会严格按照
package-lock.json
中的内容安装。
npm install
则会获取最新的匹配版本。

After Updating Any Component

更新任意组件后

  1. Update all related components together
  2. Recompile contracts
  3. Restart the proof server with the new Docker image
  4. Run your test suite
  1. 同步更新所有相关组件
  2. 重新编译合约
  3. 使用新的Docker镜像重启证明服务器
  4. 运行测试套件

5) Common Mistakes and Fixes

5) 常见错误及修复方法

Forgot
disclose()
on Ledger Write

账本写入时忘记添加
disclose()

compact
// wrong — compiler error
balance = getBalance();

// correct
balance = disclose(getBalance());
compact
// 错误 —— 编译器报错
balance = getBalance();

// 正确
balance = disclose(getBalance());

Forgot
disclose()
on Return Value

返回值忘记添加
disclose()

compact
// wrong — compiler error (comparison of witness data)
export circuit check(n: Uint<64>): Boolean {
  return getSecret() > n;
}

// correct — declare the disclosure
export circuit check(n: Uint<64>): Boolean {
  return disclose(getSecret()) > n;
}
compact
// 错误 —— 编译器报错(见证数据比较)
export circuit check(n: Uint<64>): Boolean {
  return getSecret() > n;
}

// 正确 —— 声明披露
export circuit check(n: Uint<64>): Boolean {
  return disclose(getSecret()) > n;
}

return
Inside
for
Loop

for
循环内使用
return

compact
// wrong — static error
circuit findFirst(v: Vector<4, Field>, target: Field): Boolean {
  for (const x of v) {
    if (x == target) return true;
  }
  return false;
}

// correct — use fold
circuit findFirst(v: Vector<4, Field>, target: Field): Boolean {
  return fold((found, x) => found || x == target, false, v);
}
compact
// 错误 —— 静态错误
circuit findFirst(v: Vector<4, Field>, target: Field): Boolean {
  for (const x of v) {
    if (x == target) return true;
  }
  return false;
}

// 正确 —— 使用fold
circuit findFirst(v: Vector<4, Field>, target: Field): Boolean {
  return fold((found, x) => found || x == target, false, v);
}

Recursive Circuit

递归电路

compact
// wrong — static error: recursion not allowed
circuit factorial(n: Uint<64>): Uint<64> {
  return n == 0 ? 1 : n * factorial(n - 1);
}
Rewrite using
fold
or explicit unrolling. Compact requires finite circuits.
compact
// 错误 —— 静态错误:不允许递归
circuit factorial(n: Uint<64>): Uint<64> {
  return n == 0 ? 1 : n * factorial(n - 1);
}
使用
fold
或显式展开重写。Compact要求电路必须是有限的。

Narrowing Cast Overflows at Runtime

运行时窄化转换溢出

compact
const x: Uint<64> = 1000;
const y = x as Uint<8>;  // dynamic error: 1000 doesn't fit
Always verify the value fits before casting. Use
assert
or bounded types.
compact
const x: Uint<64> = 1000;
const y = x as Uint<8>;  // 动态错误:1000超出范围
转换前务必验证值是否符合范围。使用
assert
或有界类型。

Uninitialized Nested Ledger State

未初始化的嵌套账本状态

compact
ledger fld: Map<Boolean, Map<Field, Counter>>;

// wrong — dynamic error (inner map not initialized)
export circuit increment(b: Boolean, n: Field): [] {
  fld.lookup(b).lookup(n) += 1;
}

// correct — initialize first
export circuit init(b: Boolean): [] {
  fld.insert(disclose(b), default<Map<Field, Counter>>);
}
compact
ledger fld: Map<Boolean, Map<Field, Counter>>;

// 错误 —— 动态错误(内部映射未初始化)
export circuit increment(b: Boolean, n: Field): [] {
  fld.lookup(b).lookup(n) += 1;
}

// 正确 —— 先初始化
export circuit init(b: Boolean): [] {
  fld.insert(disclose(b), default<Map<Field, Counter>>);
}

transientHash
Result Used Without
disclose()

使用
transientHash
结果时未添加
disclose()

compact
// wrong — compiler error (witness-tainted)
ledger h: Field;
export circuit store(v: Field): [] {
  h = transientHash<Field>(v);
}

// correct — declare disclosure
h = disclose(transientHash<Field>(v));

// or use transientCommit (nonce provides hiding, no disclose needed)
h = transientCommit<Field>(v, nonce);
compact
// 错误 —— 编译器报错(见证数据污染)
ledger h: Field;
export circuit store(v: Field): [] {
  h = transientHash<Field>(v);
}

// 正确 —— 声明披露
h = disclose(transientHash<Field>(v));

// 或使用transientCommit(随机数提供隐藏,无需disclose)
h = transientCommit<Field>(v, nonce);

6) Common Environment Issues

6) 常见环境问题

ErrorCauseFix
compact: command not found
Binary not on PATH
export PATH="$HOME/.compact/bin:$PATH"
ERR_UNSUPPORTED_DIR_IMPORT
Node.js tried to import directoryOpen new terminal, clear caches
Docker connection errorsDocker Desktop not runningStart Docker Desktop
Port 6300 in useAnother container on same port
-p 6301:6300
Version mismatch at runtimeOutdated runtime packageCheck compatibility matrix, update
错误原因修复方法
compact: command not found
二进制文件不在PATH中
export PATH="$HOME/.compact/bin:$PATH"
ERR_UNSUPPORTED_DIR_IMPORT
Node.js尝试导入目录打开新终端,清除缓存
Docker连接错误Docker Desktop未运行启动Docker Desktop
端口6300被占用其他容器使用同一端口使用
-p 6301:6300
运行时版本不匹配运行时包过时检查兼容性矩阵,更新版本

7) Version Check Script

7) 版本检查脚本

Run this before filing a bug report:
bash
#!/bin/bash
echo "=== Midnight Version Check ==="
echo "CLI:"; compact --version || echo "not found"
echo "Compiler:"; compact compile --version || echo "not found"
echo "Runtime:"
npm list --depth=0 | grep @midnight-ntwrk || echo "none found"
echo "Node.js:"; node --version
echo "Compare with: docs.midnight.network/relnotes/support-matrix"
提交bug报告前运行此脚本:
bash
#!/bin/bash
echo "=== Midnight Version Check ==="
echo "CLI:"; compact --version || echo "not found"
echo "Compiler:"; compact compile --version || echo "not found"
echo "Runtime:"
npm list --depth=0 | grep @midnight-ntwrk || echo "none found"
echo "Node.js:"; node --version
echo "Compare with: docs.midnight.network/relnotes/support-matrix"

8) Getting Help

8) 获取帮助

If stuck after checking these notes:
  1. Discord
    #dev-chat
    — post your error message and version details
  2. FAQ — docs.midnight.network/troubleshoot/faq
  3. Forum — forum.midnight.network
Always include:
  • Output of the version check script
  • The full error message
  • The
    .compact
    file (or relevant excerpt)
  • What you expected vs what happened
如果查看这些说明后仍无法解决问题:
  1. Discord
    #dev-chat
    —— 发布你的错误消息和版本详情
  2. FAQ —— docs.midnight.network/troubleshoot/faq
  3. 论坛 —— forum.midnight.network
请务必提供:
  • 版本检查脚本的输出
  • 完整的错误消息
  • .compact
    文件(或相关片段)
  • 预期结果与实际结果的差异

9) Quick Recap

9) 快速回顾

  • Static errors — compiler catches them before output. Check the messages.
  • Dynamic errors — happen at runtime inside generated code. Read the line numbers.
  • Undeclared disclosure trace — read bottom to top, it traces the path from origin to disclosure.
  • Use
    --skip-zk
    during development. Enable for final builds.
  • Lock exact versions in
    package.json
    . Use
    npm ci
    .
  • Check the compatibility matrix before updating any component.
  • After any update: recompile, restart proof server, run tests.
  • 静态错误 —— 编译器在生成输出前捕获。请检查提示消息。
  • 动态错误 —— 发生在运行时的生成代码内部。请查看行号。
  • 未声明披露追踪 —— 从下往上阅读,它追踪了从源头到披露点的路径。
  • 开发时使用
    --skip-zk
    。最终构建时启用该步骤。
  • 在
    package.json
    中锁定精确版本
    。使用
    npm ci
    。
  • 更新任何组件前检查兼容性矩阵。
  • 更新后: 重新编译、重启证明服务器、运行测试。