diff --git a/.github/workflows/release-desktop.yml b/.github/workflows/release-desktop.yml
index f9fce38..bb29a32 100644
--- a/.github/workflows/release-desktop.yml
+++ b/.github/workflows/release-desktop.yml
@@ -32,9 +32,9 @@ jobs:
platform: linux
runs-on: ${{ matrix.os }}
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v7
- - uses: actions/setup-node@v4
+ - uses: actions/setup-node@v7
with:
node-version: 22
cache: npm
@@ -218,7 +218,7 @@ jobs:
- name: Record artifact hashes
run: node scripts/write-release-checksums.mjs release-assets "${{ matrix.platform }}"
- - uses: actions/upload-artifact@v4
+ - uses: actions/upload-artifact@v7
with:
name: desktop-${{ matrix.platform }}
path: release-assets/*
@@ -232,9 +232,9 @@ jobs:
permissions:
contents: write
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v7
- - uses: actions/download-artifact@v4
+ - uses: actions/download-artifact@v8
with:
pattern: desktop-*
path: release-assets
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a4e016c..b397712 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,27 @@
# Changelog
+## 0.2.6 — 2026-08-09
+
+- Updated the default SHA-256-pinned official SilverScript compiler to `cb34aa5e6a598f9e461c4ad7014279ba89251d8d`; the `2a3961c` legacy profile remains available for reproducibility.
+- Added compatibility findings and compiler-backed regression tests for duplicate function names, entry parameters that shadow contract fields, and non-numeric ordered comparisons; documented the new `g16.verify` Groth16 builtin.
+- Recompiled every built-in template with realistic constructor arguments against the new compiler and retained TN10-only, fail-closed deployment policy for experimental templates and KCC721.
+- Fixed a false-positive `SS002` warning for same-input `scriptPubKey` continuation checks.
+- Fixed stale toolchain commit text, stale “no work open” state, and non-localized template project names; applying a template now preserves the user's project name.
+- Improved wallet onboarding after the one-time recovery backup by selecting the new wallet and explicitly focusing the password-to-connect step without retaining the password.
+- Updated vulnerable transitive `postcss` and `nanoid` versions; `npm audit` now reports zero known vulnerabilities.
+- Updated the official GitHub Actions used by the desktop release pipeline to their current Node 24-based majors.
+
+## 0.2.6 — 2026-08-09(中文)
+
+- 默认官方 SilverScript 编译器升级并固定到 `cb34aa5e6a598f9e461c4ad7014279ba89251d8d`,继续保留 `2a3961c` 旧版复现档案;所有二进制均校验 SHA-256。
+- 增加重复函数名、入口参数遮蔽契约字段、非数值有序比较的兼容性提示与编译器回归测试,并记录新的 `g16.verify` Groth16 内建函数。
+- 使用真实构造参数和新编译器重新完整编译全部内置模板;实验模板与 KCC721 继续仅限 TN10,并保持部署失效关闭。
+- 修复同一输入 `scriptPubKey` 延续检查被错误报告为 `SS002` 的误报。
+- 修复工具链提交号陈旧、已打开项目仍显示“没有打开的工作”、模板项目名称未本地化;应用模板时不再覆盖用户自定义项目名。
+- 改进一次性助记词备份后的钱包引导:自动选中新钱包并聚焦重新输入密码连接,同时不保留钱包密码。
+- 更新存在安全公告的间接依赖 `postcss` 与 `nanoid`;`npm audit` 现为零已知漏洞。
+- 将桌面发布流水线使用的 GitHub 官方 Actions 升级到当前基于 Node 24 的主版本。
+
## 0.2.5 — 2026-08-07
- Added the minimum macOS Hardened Runtime JIT entitlement required by the bundled Node/V8 sidecar, fixing immediate `SIGTRAP` termination and local-service startup failure on Apple Silicon.
diff --git a/README.md b/README.md
index b777ade..2d17cf2 100644
--- a/README.md
+++ b/README.md
@@ -35,7 +35,7 @@ AI 只负责生成候选方案和辅助审查,不能解锁钱包、签名交
- 中英文界面;首次启动自动读取系统语言,并在系统语言既非中文也非英文时使用时区辅助判断。
- 用户手动切换语言后,本机选择优先于自动识别。
- 本地项目工作区,可创建、切换和删除未使用的工作。
-- 双编译器兼容档案:默认固定 `kaspanet/silverscript@4b0e1cd69739934f92c3ac4df1bb13d912418b2b`,并保留 `2a3961c` 旧版用于复现;两者都校验二进制 SHA-256。
+- 双编译器兼容档案:默认固定 `kaspanet/silverscript@cb34aa5e6a598f9e461c4ad7014279ba89251d8d`,并保留 `2a3961c` 旧版用于复现;两者都校验二进制 SHA-256。
- 内置破坏性变更扫描与安全迁移,识别 `entry`、`checkMsgSig`、`outpointTxId`、artifact `bytecode` 等升级差异;无法安全自动迁移的 `.reverse()` 和位运算会要求人工审查。
- 固定 Kascov 来源提交构建的本地交易预检引擎。
- 支持 OpenAI、Anthropic、Gemini、OpenRouter、Ollama 和 OpenAI-compatible 接口。
@@ -149,7 +149,7 @@ npm run desktop:build
- 固定版本本地预检引擎。
- Kaspa WASM、契约模板、知识库和第三方许可证。
-原生安装包必须在对应系统上构建和测试,macOS、Windows 和 Linux 的二进制不能互相替代。v0.2.5 发布流程在原生 GitHub Runner 上分别生成 macOS Apple Silicon DMG、Windows x64 NSIS/MSI,以及 Linux x86_64 DEB/AppImage,并附带 SHA-256 校验文件。macOS 包必须使用已签名 sidecar 通过 V8、健康接口和模板接口测试;Windows 包必须通过安装资源启动、健康接口和模板接口测试。
+原生安装包必须在对应系统上构建和测试,macOS、Windows 和 Linux 的二进制不能互相替代。发布流程在原生 GitHub Runner 上分别生成 macOS Apple Silicon DMG、Windows x64 NSIS/MSI,以及 Linux x86_64 DEB/AppImage,并附带 SHA-256 校验文件。macOS 包必须使用已签名 sidecar 通过 V8、健康接口和模板接口测试;Windows 包必须通过安装资源启动、健康接口和模板接口测试。
公开包目前没有商业代码签名:macOS 使用 ad-hoc 签名且未公证,Windows 未进行 Authenticode 签名,Linux 未进行发行版签名。首次启动可能出现系统安全提示;请只从本仓库 Release 下载并核对 SHA-256。
@@ -288,7 +288,7 @@ AI is limited to candidate generation and review assistance. It cannot unlock wa
- Chinese and English UI with automatic system-language detection and time-zone fallback.
- A manual language choice always overrides future automatic detection.
- Local project workspace with explicit create, switch, and delete actions.
-- Dual compiler profiles: the default is pinned to `kaspanet/silverscript@4b0e1cd69739934f92c3ac4df1bb13d912418b2b`, while `2a3961c` remains available for reproducible legacy builds; both binaries are SHA-256 verified.
+- Dual compiler profiles: the default is pinned to `kaspanet/silverscript@cb34aa5e6a598f9e461c4ad7014279ba89251d8d`, while `2a3961c` remains available for reproducible legacy builds; both binaries are SHA-256 verified.
- Built-in breaking-change detection and safe migration for `entry`, `checkMsgSig`, `outpointTxId`, and artifact `bytecode`; removed `.reverse()` and bitwise typing changes require manual review.
- Pinned Kascov-derived local transaction preflight engine.
- OpenAI, Anthropic, Gemini, OpenRouter, Ollama, and OpenAI-compatible providers.
@@ -375,7 +375,7 @@ npm run desktop:build
The desktop bundle contains the frontend, local Node.js sidecar, pinned `silverc`, pinned local preflight engine, Kaspa WASM, templates, knowledge resources, and third-party license notices.
-Native installers must be built and tested on their target operating systems; macOS, Windows, and Linux binaries are not interchangeable. The v0.2.5 release pipeline produces a macOS Apple Silicon DMG, Windows x64 NSIS/MSI packages, and Linux x86_64 DEB/AppImage packages on native GitHub Runners, with SHA-256 checksum files. The macOS package must pass signed-sidecar V8, health-endpoint, and template-endpoint tests; the Windows package must pass packaged-resource startup, health-endpoint, and template-endpoint tests.
+Native installers must be built and tested on their target operating systems; macOS, Windows, and Linux binaries are not interchangeable. The release pipeline produces a macOS Apple Silicon DMG, Windows x64 NSIS/MSI packages, and Linux x86_64 DEB/AppImage packages on native GitHub Runners, with SHA-256 checksum files. The macOS package must pass signed-sidecar V8, health-endpoint, and template-endpoint tests; the Windows package must pass packaged-resource startup, health-endpoint, and template-endpoint tests.
The public packages are not commercially code-signed: macOS is ad-hoc signed and not notarized, Windows is not Authenticode-signed, and Linux is not distribution-signed. The operating system may show a warning on first launch. Download only from this repository's Releases and verify the SHA-256 checksum.
diff --git a/config/compiler-profiles.json b/config/compiler-profiles.json
index 0bba6b3..929b4a0 100644
--- a/config/compiler-profiles.json
+++ b/config/compiler-profiles.json
@@ -1,21 +1,24 @@
{
- "defaultProfileId": "latest-4b0e1cd",
+ "defaultProfileId": "latest-cb34aa5",
"profiles": [
{
- "id": "latest-4b0e1cd",
- "label": "SilverScript latest (4b0e1cd)",
- "upstreamCommit": "4b0e1cd69739934f92c3ac4df1bb13d912418b2b",
+ "id": "latest-cb34aa5",
+ "label": "SilverScript latest (cb34aa5)",
+ "upstreamCommit": "cb34aa5e6a598f9e461c4ad7014279ba89251d8d",
"binary": "bin/silverc-latest",
"artifactBytecodeField": "bytecode",
"syntaxGeneration": 2,
"status": "experimental",
"networkPolicy": "tn10-only",
- "releasedAt": "2026-08-03",
+ "releasedAt": "2026-08-09",
"notes": [
"Uses entry syntax for public entrypoints",
"Exposes transaction introspection as outpointTxId",
"Uses checkMsgSig for arbitrary-message signatures",
- "Compiler JSON calls the emitted program bytecode"
+ "Compiler JSON calls the emitted program bytecode",
+ "Rejects duplicate function names and entry parameters that shadow contract fields",
+ "Restricts ordered comparisons to numeric operands",
+ "Adds the variable-input g16.verify Groth16 verifier builtin"
]
},
{
@@ -40,7 +43,7 @@
"severity": "error",
"introducedBy": "0f99803",
"fromProfile": "legacy-2a3961c",
- "toProfile": "latest-4b0e1cd",
+ "toProfile": "latest-cb34aa5",
"pattern": "\\bentrypoint\\s+function\\b",
"messageZh": "公开入口语法已从 entrypoint function 改为 entry。",
"messageEn": "Public entrypoint syntax changed from entrypoint function to entry.",
@@ -51,7 +54,7 @@
"severity": "error",
"introducedBy": "782a4d7",
"fromProfile": "legacy-2a3961c",
- "toProfile": "latest-4b0e1cd",
+ "toProfile": "latest-cb34aa5",
"pattern": "\\bcheckSigFromStack\\b",
"messageZh": "任意消息签名内建函数已改名为 checkMsgSig。",
"messageEn": "The arbitrary-message signature builtin was renamed to checkMsgSig.",
@@ -62,7 +65,7 @@
"severity": "error",
"introducedBy": "65421cf",
"fromProfile": "legacy-2a3961c",
- "toProfile": "latest-4b0e1cd",
+ "toProfile": "latest-cb34aa5",
"pattern": "\\.outpointTransactionHash\\b",
"messageZh": "输入 outpoint 字段已改名为 outpointTxId。",
"messageEn": "The input outpoint field was renamed to outpointTxId.",
@@ -73,7 +76,7 @@
"severity": "error",
"introducedBy": "6869e7d",
"fromProfile": "legacy-2a3961c",
- "toProfile": "latest-4b0e1cd",
+ "toProfile": "latest-cb34aa5",
"pattern": "\\.reverse\\s*\\(",
"messageZh": ".reverse() 已删除,必须显式重写字节顺序逻辑并重新测试。",
"messageEn": ".reverse() was removed; rewrite byte ordering explicitly and retest it.",
@@ -84,7 +87,7 @@
"severity": "integration",
"introducedBy": "4d88ded",
"fromProfile": "legacy-2a3961c",
- "toProfile": "latest-4b0e1cd",
+ "toProfile": "latest-cb34aa5",
"pattern": null,
"messageZh": "编译产物 JSON 字段由 script 改为 bytecode;外部工具必须兼容两个字段。",
"messageEn": "The compiler artifact JSON field changed from script to bytecode; integrations must support both.",
@@ -95,7 +98,7 @@
"severity": "manual-review",
"introducedBy": "8b74812",
"fromProfile": "legacy-2a3961c",
- "toProfile": "latest-4b0e1cd",
+ "toProfile": "latest-cb34aa5",
"pattern": "(?:^|[^&|])(?:<<|>>|\\^|&|\\|)(?:[^&|]|$)",
"messageZh": "位运算现在仅接受 bytes;请人工确认操作数类型。",
"messageEn": "Bitwise operators now accept bytes only; review operand types manually.",
diff --git a/docs/kcc721-experimental.md b/docs/kcc721-experimental.md
index 19b3a74..6771ee8 100644
--- a/docs/kcc721-experimental.md
+++ b/docs/kcc721-experimental.md
@@ -6,7 +6,7 @@ Studio 内置的 KCC721 包改编自 `KaspaHUB21/KCC721` v0.2 社区草案,保
本包只允许 `tn10`,风险等级为 `high-experimental`。普通单 Covenant 部署路径被明确禁用,因为 Collection/Ticket/NFT 创世需要专用的多合约 builder 正确计算模板片段、模板哈希、Covenant ID 和 output binding。当前完成的是:
-- 四份源码在官方 `silverc@4b0e1cd` 下完整编译。
+- 四份源码在官方 `silverc@cb34aa5` 下完整编译。
- 模板使用三步配置向导,不再要求用户手填元数据摘要。名称、描述、图片 URI、外部链接和属性会先规范化为确定性 JSON,再由前后端分别计算并核对 SHA-256。
- “新集合”明确标记为编译预览,内部使用不可部署的全零哨兵;只有“导入已有 TN10 集合”模式接受从真实创世输出核验的 Collection Covenant ID。
- NFT 所有者变更绑定独立 P2PK co-spend 输入。
@@ -34,7 +34,7 @@ The bundled KCC721 pack is adapted from the community `KaspaHUB21/KCC721` v0.2 d
The pack is restricted to `tn10` and marked `high-experimental`. Ordinary single-covenant deployment is explicitly blocked because Collection/Ticket/NFT genesis requires a dedicated multi-contract builder to calculate template segments, template hashes, covenant IDs, and output bindings correctly. The current implementation provides:
-- Full compilation of all four sources with official `silverc@4b0e1cd`.
+- Full compilation of all four sources with official `silverc@cb34aa5`.
- A three-step setup wizard that no longer asks users to type a metadata digest. Name, description, image URI, external URL, and attributes are canonicalized into deterministic JSON, then SHA-256 is independently recomputed by the client and server.
- A clearly labeled new-collection compile preview with a non-deployable internal all-zero sentinel. Only the existing-TN10-collection path accepts a Collection covenant ID verified from a real genesis output.
- NFT ownership transitions bound to a separate P2PK co-spend input.
diff --git a/docs/releases/v0.2.6.md b/docs/releases/v0.2.6.md
new file mode 100644
index 0000000..e4216ba
--- /dev/null
+++ b/docs/releases/v0.2.6.md
@@ -0,0 +1,51 @@
+## Kaspa SilverScript Studio v0.2.6
+
+This release updates Studio to the current official experimental SilverScript compiler snapshot and tightens compatibility, security, and desktop usability without widening mainnet access.
+
+### Compiler and covenant verification
+
+- Pins official `kaspanet/silverscript@cb34aa5e6a598f9e461c4ad7014279ba89251d8d` and verifies the packaged compiler by SHA-256.
+- Detects duplicate function names, entry parameters that shadow contract fields, and obvious non-numeric ordered comparisons before compilation.
+- Documents the new variable-input `g16.verify` Groth16 builtin.
+- Fully compiles all built-in templates with realistic constructor arguments on the new toolchain.
+- Keeps the legacy `2a3961c` profile for reproducible historical builds.
+- Keeps experimental templates and the KCC721 research pack TN10-only and fail-closed for unsupported deployment paths.
+
+### Reliability, security, and usability
+
+- Removes a false `SS002` warning from valid same-input `scriptPubKey` continuation checks.
+- Shows the actual current toolchain commit in the sidebar.
+- Correctly restores “Saved locally” when a project opens and localizes new template project names.
+- Preserves a user's project name when applying a different template.
+- Makes the post-backup wallet connection step explicit without caching the wallet password.
+- Updates transitive `postcss` and `nanoid` packages; the release lockfile passes `npm audit` with zero known vulnerabilities.
+- Moves the desktop release workflow to the current Node 24-based official GitHub Actions.
+
+Mainnet remains disabled by default. SilverScript itself remains experimental, so this release is not a claim that arbitrary contracts are mainnet-ready.
+
+---
+
+## 中文说明
+
+本版将 Studio 升级到当前官方 SilverScript 实验版编译器快照,并加强兼容性检查、安全性与桌面端易用性;不会扩大主网权限。
+
+### 编译器与契约验证
+
+- 固定官方 `kaspanet/silverscript@cb34aa5e6a598f9e461c4ad7014279ba89251d8d`,打包编译器继续执行 SHA-256 校验。
+- 编译前检查重复函数名、入口参数遮蔽契约字段,以及明显的非数值有序比较。
+- 记录新的可变公共输入 `g16.verify` Groth16 内建函数。
+- 使用真实构造参数在新工具链上完整编译全部内置模板。
+- 保留 `2a3961c` 旧版档案,用于历史项目复现。
+- 实验模板与 KCC721 研究包继续仅限 TN10;不受支持的部署路径保持失效关闭。
+
+### 稳定性、安全与易用性
+
+- 消除有效的同一输入 `scriptPubKey` 延续检查产生的 `SS002` 误报。
+- 侧栏显示真实的当前工具链提交号。
+- 打开项目后正确恢复“已保存到本机”,新模板项目名称会跟随界面语言。
+- 将模板应用到已有项目时保留用户自己的项目名称。
+- 助记词备份完成后明确引导重新输入密码连接,同时不缓存钱包密码。
+- 更新间接依赖 `postcss` 与 `nanoid`;发布锁文件通过 `npm audit`,已知漏洞为零。
+- 桌面发布流水线升级到当前基于 Node 24 的 GitHub 官方 Actions。
+
+主网继续默认禁用。SilverScript 本身仍处于实验阶段,因此本次发布不代表任意契约已经可以安全用于主网。
diff --git a/docs/studio-0.2-architecture.md b/docs/studio-0.2-architecture.md
index 9524732..ce0508b 100644
--- a/docs/studio-0.2-architecture.md
+++ b/docs/studio-0.2-architecture.md
@@ -4,7 +4,7 @@
### 编译器兼容档案
-`config/compiler-profiles.json` 是可提交的兼容性清单,`config/compiler.json` 是本机生成的二进制路径、构建时间和 SHA-256 清单。默认档案固定官方 SilverScript `4b0e1cd69739934f92c3ac4df1bb13d912418b2b`,旧版 `2a3961cadc76bb16a425042172ffe32481da89b5` 只用于复现已有项目。
+`config/compiler-profiles.json` 是可提交的兼容性清单,`config/compiler.json` 是本机生成的二进制路径、构建时间和 SHA-256 清单。默认档案固定官方 SilverScript `cb34aa5e6a598f9e461c4ad7014279ba89251d8d`,旧版 `2a3961cadc76bb16a425042172ffe32481da89b5` 只用于复现已有项目。
升级检查会报告已知变化,并只自动替换无歧义的名称。`.reverse()` 删除、字节序、位运算类型和任何状态布局变化必须人工审查。迁移后仍必须使用真实构造参数完整编译并进行对抗性交易测试。
@@ -48,7 +48,7 @@ Studio 当前依次尝试:节点原生 Covenant ID 查询(节点支持时)
### Compiler compatibility profiles
-`config/compiler-profiles.json` is the committed compatibility catalog. The generated `config/compiler.json` records local binary paths, build times, and SHA-256 hashes. The default profile pins official SilverScript commit `4b0e1cd69739934f92c3ac4df1bb13d912418b2b`; `2a3961cadc76bb16a425042172ffe32481da89b5` is retained only for reproducible legacy builds.
+`config/compiler-profiles.json` is the committed compatibility catalog. The generated `config/compiler.json` records local binary paths, build times, and SHA-256 hashes. The default profile pins official SilverScript commit `cb34aa5e6a598f9e461c4ad7014279ba89251d8d`; `2a3961cadc76bb16a425042172ffe32481da89b5` is retained only for reproducible legacy builds.
Compatibility checks report known changes and automatically apply only unambiguous renames. Removed `.reverse()`, byte ordering, bitwise typing, and any state-layout change require manual review. Every migration still requires a full compile with realistic constructor arguments and adversarial transaction tests.
diff --git a/index.html b/index.html
index 8e98b6f..f3179db 100644
--- a/index.html
+++ b/index.html
@@ -38,7 +38,7 @@
契约项目
工具链
-
SilverScript Skill2a3961c
+
SilverScript Skill—
本地编译器—
默认网络TN10
@@ -153,7 +153,7 @@ 契约项目
Source SHA-256—
Program SHA-256—
Compiler SHA-256—
- Upstream Commit2a3961c…
+ Upstream Commit—
完整编译后,这里会显示 ABI、程序哈希和启发式安全检查结果。
diff --git a/knowledge/kaspa-silverscript/references/official-baseline.md b/knowledge/kaspa-silverscript/references/official-baseline.md
index 6b616df..35b9902 100644
--- a/knowledge/kaspa-silverscript/references/official-baseline.md
+++ b/knowledge/kaspa-silverscript/references/official-baseline.md
@@ -3,8 +3,8 @@
## Verified snapshot
- Repository:
-- Verified commit: `4b0e1cd69739934f92c3ac4df1bb13d912418b2b`
-- Verified date: 2026-08-06
+- Verified commit: `cb34aa5e6a598f9e461c4ad7014279ba89251d8d`
+- Verified date: 2026-08-09
- Compiler/language status: experimental
- Official recommendation at this snapshot: use bytecode artifacts on
testnet-10 until the first stable v1 release.
@@ -15,17 +15,17 @@ deployment, compatibility, or mainnet-readiness questions.
## Primary sources
- Project status and debugger:
-
+
- Language tutorial:
-
+
- Covenant declaration semantics:
-
+
- Built-ins and cross-template validation:
-
+
- KCC20 book:
- Official application examples, including chess:
-
+
## Snapshot capabilities
@@ -58,6 +58,10 @@ deployment, compatibility, or mainnet-readiness questions.
- `.reverse()` was removed and bitwise operations now require byte operands.
Byte ordering and integer/byte casts therefore require manual review rather
than blind source replacement.
+- Commit `cb34aa5` rejects duplicate function names, entrypoint parameters that
+ shadow contract fields, and non-numeric ordered comparisons. It also fixes
+ fixed/dynamic array sizing and cast validation. Commit `5aa0886` adds the
+ variable-input `g16.verify` Groth16 verifier built-in.
## Terminology discipline
diff --git a/knowledge/kaspa-silverscript/references/upstream.json b/knowledge/kaspa-silverscript/references/upstream.json
index 2a7a4dc..59fd3eb 100644
--- a/knowledge/kaspa-silverscript/references/upstream.json
+++ b/knowledge/kaspa-silverscript/references/upstream.json
@@ -1,8 +1,8 @@
{
"repository": "https://github.com/kaspanet/silverscript.git",
"branch": "master",
- "verified_commit": "4b0e1cd69739934f92c3ac4df1bb13d912418b2b",
- "verified_at": "2026-08-06",
+ "verified_commit": "cb34aa5e6a598f9e461c4ad7014279ba89251d8d",
+ "verified_at": "2026-08-09",
"status": "experimental",
"recommended_network": "testnet-10"
}
diff --git a/package-lock.json b/package-lock.json
index 5ea9e0f..271b523 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "kaspa-silverscript-studio",
- "version": "0.2.5",
+ "version": "0.2.6",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "kaspa-silverscript-studio",
- "version": "0.2.5",
+ "version": "0.2.6",
"license": "MIT",
"dependencies": {
"@kluster/kaspa-wasm": "2.0.1",
@@ -1754,7 +1754,9 @@
"license": "MIT"
},
"node_modules/nanoid": {
- "version": "3.3.16",
+ "version": "3.3.18",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz",
+ "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==",
"dev": true,
"funding": [
{
@@ -1836,7 +1838,9 @@
}
},
"node_modules/postcss": {
- "version": "8.5.21",
+ "version": "8.5.26",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz",
+ "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==",
"dev": true,
"funding": [
{
@@ -1854,7 +1858,7 @@
],
"license": "MIT",
"dependencies": {
- "nanoid": "^3.3.16",
+ "nanoid": "^3.3.17",
"picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
},
diff --git a/package.json b/package.json
index 1690391..1ada578 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "kaspa-silverscript-studio",
- "version": "0.2.5",
+ "version": "0.2.6",
"private": true,
"type": "module",
"description": "Local bilingual AI-assisted SilverScript contract studio for Kaspa",
diff --git a/scripts/build-silverc.mjs b/scripts/build-silverc.mjs
index d6c40de..3ce0a20 100644
--- a/scripts/build-silverc.mjs
+++ b/scripts/build-silverc.mjs
@@ -8,7 +8,7 @@ import { fileURLToPath } from "node:url";
import { cargoReleaseBinary, executableName, makeExecutable } from "./platform-binaries.mjs";
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
-const latestCommit = "4b0e1cd69739934f92c3ac4df1bb13d912418b2b";
+const latestCommit = "cb34aa5e6a598f9e461c4ad7014279ba89251d8d";
const legacyCommit = "2a3961cadc76bb16a425042172ffe32481da89b5";
const work = fs.mkdtempSync(path.join(os.tmpdir(), "silverstudio-silverc-"));
@@ -55,7 +55,7 @@ function buildProfile({ id, commit, outputName, configuredSource }) {
try {
fs.mkdirSync(path.join(root, "config"), { recursive: true });
const latestBin = buildProfile({
- id: "latest-4b0e1cd",
+ id: "latest-cb34aa5",
commit: latestCommit,
outputName: "silverc-latest",
configuredSource: process.env.SILVERSCRIPT_LATEST_SOURCE || process.env.SILVERSCRIPT_SOURCE || ""
@@ -69,9 +69,9 @@ try {
const latestSha256 = sha256(latestBin);
const legacySha256 = sha256(legacyBin);
const manifest = {
- defaultProfileId: "latest-4b0e1cd",
+ defaultProfileId: "latest-cb34aa5",
profiles: {
- "latest-4b0e1cd": {
+ "latest-cb34aa5": {
bin: latestBin,
sha256: latestSha256,
upstreamCommit: latestCommit,
diff --git a/server/compiler.mjs b/server/compiler.mjs
index a0a825d..3fb602f 100644
--- a/server/compiler.mjs
+++ b/server/compiler.mjs
@@ -64,6 +64,77 @@ function lineAt(source, offset) {
return source.slice(0, offset).split("\n").length;
}
+function latestHardeningFindings(source, target) {
+ if (target.upstreamCommit !== "cb34aa5e6a598f9e461c4ad7014279ba89251d8d") return [];
+ const findings = [];
+ const declarations = new Map();
+ const functionPattern = /\b(?:entry|function)\s+([A-Za-z_][A-Za-z0-9_]*)\s*\(/g;
+ let match;
+ while ((match = functionPattern.exec(source))) {
+ const previous = declarations.get(match[1]);
+ if (previous !== undefined) {
+ findings.push({
+ id: "duplicate-function-name",
+ severity: "error",
+ introducedBy: "cb34aa5",
+ fromProfile: "latest-4b0e1cd",
+ toProfile: target.id,
+ pattern: null,
+ replacement: null,
+ line: lineAt(source, match.index),
+ detected: true,
+ messageZh: `函数 ${match[1]} 与第 ${lineAt(source, previous)} 行的声明重名;最新编译器会拒绝它。`,
+ messageEn: `Function ${match[1]} duplicates its declaration on line ${lineAt(source, previous)}; the latest compiler rejects it.`
+ });
+ } else declarations.set(match[1], match.index);
+ }
+
+ const bodyStart = source.indexOf("{", source.search(/\bcontract\b/));
+ const firstFunction = bodyStart < 0 ? -1 : source.slice(bodyStart + 1).search(/\b(?:entry|function)\s+[A-Za-z_]/);
+ const fieldArea = bodyStart < 0 ? "" : source.slice(bodyStart + 1, firstFunction < 0 ? source.length : bodyStart + 1 + firstFunction);
+ const fields = new Set();
+ const fieldPattern = /^\s*(?![^\n]*\bconstant\b)[A-Za-z_][A-Za-z0-9_]*(?:\[[^\]\n]*\])*\s+([A-Za-z_][A-Za-z0-9_]*)\s*=/gm;
+ while ((match = fieldPattern.exec(fieldArea))) fields.add(match[1]);
+ const entryPattern = /\bentry\s+[A-Za-z_][A-Za-z0-9_]*\s*\(([^)]*)\)/g;
+ while ((match = entryPattern.exec(source))) {
+ for (const parameter of match[1].split(",")) {
+ const name = parameter.trim().match(/([A-Za-z_][A-Za-z0-9_]*)\s*$/)?.[1];
+ if (!name || !fields.has(name)) continue;
+ findings.push({
+ id: "entry-parameter-shadows-field",
+ severity: "error",
+ introducedBy: "cb34aa5",
+ fromProfile: "latest-4b0e1cd",
+ toProfile: target.id,
+ pattern: null,
+ replacement: null,
+ line: lineAt(source, match.index),
+ detected: true,
+ messageZh: `入口参数 ${name} 与契约字段同名;最新编译器会拒绝它。`,
+ messageEn: `Entry parameter ${name} shadows a contract field; the latest compiler rejects it.`
+ });
+ }
+ }
+
+ const nonNumericComparison = /(?:"(?:[^"\\]|\\.)*"|\b(?:true|false)\b)\s*(?:<=|>=|<|>)|(?:<=|>=|<|>)\s*(?:"(?:[^"\\]|\\.)*"|\b(?:true|false)\b)/g;
+ while ((match = nonNumericComparison.exec(source))) {
+ findings.push({
+ id: "ordered-comparison-numeric-only",
+ severity: "error",
+ introducedBy: "cb34aa5",
+ fromProfile: "latest-4b0e1cd",
+ toProfile: target.id,
+ pattern: null,
+ replacement: null,
+ line: lineAt(source, match.index),
+ detected: true,
+ messageZh: "有序比较现在只接受 int 或 byte;最新编译器会拒绝明显的字符串或布尔比较。",
+ messageEn: "Ordered comparisons now accept only int or byte; the latest compiler rejects string or boolean comparisons."
+ });
+ }
+ return findings;
+}
+
function constructorParameterTypes(source) {
const start = source.search(/\bcontract\s+[A-Za-z_][A-Za-z0-9_]*\s*\(/);
if (start < 0) return [];
@@ -153,6 +224,7 @@ export function detectBreakingChanges(source, targetProfileId = config.compiler.
if (!match[0].length) expression.lastIndex += 1;
}
}
+ findings.push(...latestHardeningFindings(text, target));
const blockers = findings.filter((finding) => finding.severity === "error");
return {
targetProfileId: target.id,
@@ -205,8 +277,9 @@ export async function staticAnalyze(source) {
});
}
}
- const scriptComparison = /scriptPubKey\s*==\s*(?!byte\[\]\s*\()([A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*)/g;
+ const scriptComparison = /scriptPubKey\s*==\s*(?!byte\[\]\s*\()([A-Za-z_]\w*(?:(?:\s*\[[^\]\n]+\])|(?:\.[A-Za-z_]\w*))*)/g;
while ((match = scriptComparison.exec(text))) {
+ if (/\.scriptPubKey$/.test(match[1].replace(/\s+/g, ""))) continue;
findings.push({
code: "SS002",
line: line(match.index),
diff --git a/server/config.mjs b/server/config.mjs
index feab0f9..cc063de 100644
--- a/server/config.mjs
+++ b/server/config.mjs
@@ -8,7 +8,7 @@ const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
dotenv.config({ path: path.join(ROOT, ".env") });
dotenv.config({ path: path.join(ROOT, ".env.local"), override: true });
-export const SILVERSCRIPT_COMMIT = "4b0e1cd69739934f92c3ac4df1bb13d912418b2b";
+export const SILVERSCRIPT_COMMIT = "cb34aa5e6a598f9e461c4ad7014279ba89251d8d";
export const SILVERSCRIPT_LEGACY_COMMIT = "2a3961cadc76bb16a425042172ffe32481da89b5";
export const NETWORKS = Object.freeze({
@@ -63,8 +63,11 @@ function loadCompilerConfig() {
builtAt: local.builtAt || legacyStored.builtAt || ""
})];
}));
+ const defaultProfileId = profiles[stored.defaultProfileId]
+ ? stored.defaultProfileId
+ : compatibility.defaultProfileId || "latest-cb34aa5";
return Object.freeze({
- defaultProfileId: stored.defaultProfileId || compatibility.defaultProfileId || "latest-4b0e1cd",
+ defaultProfileId,
profiles: Object.freeze(profiles),
breakingChanges: Object.freeze(Array.isArray(compatibility.breakingChanges) ? compatibility.breakingChanges : []),
manifestFile: file,
diff --git a/server/index.mjs b/server/index.mjs
index 3eb4cbf..1e1e0e2 100644
--- a/server/index.mjs
+++ b/server/index.mjs
@@ -125,15 +125,16 @@ app.post("/api/wallets/transfer/broadcast", async (req, res, next) => {
app.get("/api/templates", (_req, res) => res.json({ templates: templates.list() }));
app.post("/api/templates/:id/projects", (req, res, next) => {
try {
- const input = templates.projectInput(req.params.id, req.body?.network, req.body?.parameters);
+ const input = templates.projectInput(req.params.id, req.body?.network, req.body?.parameters, { language: req.body?.language });
res.status(201).json({ project: projects.create(input) });
} catch (error) { next(error); }
});
app.put("/api/projects/:projectId/template/:templateId", (req, res, next) => {
try {
if (!projects.get(req.params.projectId)) return res.status(404).json({ error: "Project not found" });
- const input = templates.projectInput(req.params.templateId, req.body?.network, req.body?.parameters);
- res.json({ project: projects.save(req.params.projectId, { ...input, artifact: null, deployment: null }) });
+ const current = projects.get(req.params.projectId);
+ const input = templates.projectInput(req.params.templateId, req.body?.network, req.body?.parameters, { language: req.body?.language });
+ res.json({ project: projects.save(req.params.projectId, { ...input, name: current.name, artifact: null, deployment: null }) });
} catch (error) { next(error); }
});
diff --git a/server/project-store.mjs b/server/project-store.mjs
index 7d4682d..100ba41 100644
--- a/server/project-store.mjs
+++ b/server/project-store.mjs
@@ -52,7 +52,7 @@ export class ProjectStore {
requirements: String(input.requirements || ""),
source: String(input.source || SAMPLE_SOURCE),
constructorArgs: Array.isArray(input.constructorArgs) ? input.constructorArgs : [],
- compilerProfileId: String(input.compilerProfileId || "latest-4b0e1cd"),
+ compilerProfileId: String(input.compilerProfileId || "latest-cb34aa5"),
templateParameters: input.templateParameters && typeof input.templateParameters === "object" ? input.templateParameters : {},
deployAmount: String(input.deployAmount || "0.05"),
specification: input.specification || null,
diff --git a/server/template-store.mjs b/server/template-store.mjs
index 5de3952..063cb7b 100644
--- a/server/template-store.mjs
+++ b/server/template-store.mjs
@@ -297,7 +297,7 @@ export class TemplateStore {
}
}
return {
- name: template.titleEn,
+ name: options.language === "zh" ? template.titleZh : template.titleEn,
network: selectedNetwork,
requirements: template.requirementsEn,
source: template.source,
@@ -306,7 +306,7 @@ export class TemplateStore {
templateParameters: parameters,
deployAmount,
specification: {
- title: template.titleEn,
+ title: options.language === "zh" ? template.titleZh : template.titleEn,
summaryZh: template.descriptionZh,
summaryEn: template.descriptionEn,
network: selectedNetwork === "mainnet" ? "mainnet" : "testnet-10",
diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock
index 8cc896e..26b935f 100644
--- a/src-tauri/Cargo.lock
+++ b/src-tauri/Cargo.lock
@@ -66,7 +66,7 @@ checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
[[package]]
name = "app"
-version = "0.2.5"
+version = "0.2.6"
dependencies = [
"log",
"serde",
diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml
index ec22e32..9088a05 100644
--- a/src-tauri/Cargo.toml
+++ b/src-tauri/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "app"
-version = "0.2.5"
+version = "0.2.6"
description = "Local-first Kaspa SilverScript covenant workbench"
authors = ["w00c00"]
license = "MIT"
diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json
index 7490977..73bd44d 100644
--- a/src-tauri/tauri.conf.json
+++ b/src-tauri/tauri.conf.json
@@ -1,7 +1,7 @@
{
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
"productName": "Kaspa SilverScript Studio",
- "version": "0.2.5",
+ "version": "0.2.6",
"identifier": "io.kaspa.silverscript-studio",
"build": {
"frontendDist": "../dist",
diff --git a/src/main.js b/src/main.js
index 572ea5b..04276b4 100644
--- a/src/main.js
+++ b/src/main.js
@@ -120,7 +120,8 @@ const state = {
localOperationProjectId: "",
lifecycleInviteProjectId: "",
pendingDeleteProjectId: "",
- pendingSourceMigration: null
+ pendingSourceMigration: null,
+ pendingCreatedWalletId: ""
};
function tr(key) { return copy[state.language]?.[key] || copy.zh[key] || key; }
@@ -254,7 +255,7 @@ function projectPayload() {
requirements: $("#requirements").value,
source: $("#source-editor").value,
constructorArgs,
- compilerProfileId: $("#compiler-profile").value || state.config?.compiler?.defaultProfileId || "latest-4b0e1cd",
+ compilerProfileId: $("#compiler-profile").value || state.config?.compiler?.defaultProfileId || "latest-cb34aa5",
templateParameters: state.project?.templateParameters || {},
deployAmount: $("#deploy-amount").value,
specification: state.project?.specification || null,
@@ -316,11 +317,13 @@ function loadProjectIntoUi(project) {
state.draft = null;
$("#project-name").disabled = false;
$("#no-project-banner").hidden = true;
+ $("#save-label").textContent = tr("savedLocal");
+ $("#save-dot").classList.remove("saving");
$("#project-name").value = project.name || "Untitled Covenant";
$("#requirements").value = project.requirements || "";
$("#source-editor").value = project.source || "";
$("#constructor-args").value = JSON.stringify(project.constructorArgs || [], null, 2);
- $("#compiler-profile").value = project.compilerProfileId || project.artifact?.compiler?.id || project.review?.compilerProfileId || state.config?.compiler?.defaultProfileId || "latest-4b0e1cd";
+ $("#compiler-profile").value = project.compilerProfileId || project.artifact?.compiler?.id || project.review?.compilerProfileId || state.config?.compiler?.defaultProfileId || "latest-cb34aa5";
renderCompilerProfileHelp();
$("#deploy-amount").value = project.deployAmount || "0.05";
$("#deploy-network").value = project.network || "tn10";
@@ -384,7 +387,7 @@ function renderProjectList() {
async function createProject(templateId = "", parameters = null) {
const name = state.language === "zh" ? "新的 Covenant" : "New Covenant";
const endpoint = templateId ? `/api/templates/${encodeURIComponent(templateId)}/projects` : "/api/projects";
- const { project } = await api(endpoint, { method: "POST", body: JSON.stringify(templateId ? { network: $("#deploy-network").value, parameters } : { name }) });
+ const { project } = await api(endpoint, { method: "POST", body: JSON.stringify(templateId ? { network: $("#deploy-network").value, parameters, language: state.language } : { name }) });
await loadProjects(false);
loadProjectIntoUi(project);
selectTab(templateId ? "source" : "design");
@@ -707,7 +710,7 @@ async function applyPendingTemplate() {
const template = state.templates.find((item) => item.id === id);
const { project } = await api(`/api/projects/${encodeURIComponent(state.project.id)}/template/${encodeURIComponent(id)}`, {
method: "PUT",
- body: JSON.stringify({ network: $("#deploy-network").value, parameters })
+ body: JSON.stringify({ network: $("#deploy-network").value, parameters, language: state.language })
});
loadProjectIntoUi(project);
await loadProjects(false);
@@ -1186,7 +1189,10 @@ async function createLocalWallet() {
clearWalletSecrets();
await loadWallets();
$("#wallet-select").value = result.wallet.id;
- if (result.recoveryPhrase) showRecoveryPhrase(result.recoveryPhrase, result.wallet.paymentSecretProtected);
+ if (result.recoveryPhrase) {
+ state.pendingCreatedWalletId = result.wallet.id;
+ showRecoveryPhrase(result.recoveryPhrase, result.wallet.paymentSecretProtected);
+ }
else toast(state.language === "zh" ? "钱包已加密导入,请在上方输入密码连接" : "Wallet imported. Enter its password above to connect", "good");
} catch (error) { toast(error.message, "bad"); }
finally { button.disabled = false; }
@@ -1963,6 +1969,7 @@ async function init() {
const session = await waitForApi();
state.token = session.token;
state.config = await api("/api/config");
+ $("#skill-state").textContent = String(state.config.skill?.upstreamCommit || "—").slice(0, 7);
renderCompilerProfiles();
$("#compiler-state").textContent = state.config.compiler.configured ? "PINNED" : "SETUP REQUIRED";
$("#compiler-state").className = state.config.compiler.configured ? "good" : "warn";
@@ -2040,7 +2047,18 @@ $("#wallet-confirm-transfer").addEventListener("click", confirmWalletTransfer);
[$("#wallet-send-recipient"), $("#wallet-send-amount"), $("#wallet-send-mainnet-phrase")].forEach((input) => input.addEventListener("input", resetTransferDraft));
$("#recovery-confirm").addEventListener("change", () => { $("#recovery-done").disabled = !$("#recovery-confirm").checked; });
$("#recovery-dialog").addEventListener("cancel", (event) => { if (!$("#recovery-confirm").checked) event.preventDefault(); });
-$("#recovery-dialog").addEventListener("close", () => { $("#recovery-phrase").textContent = ""; $("#recovery-passphrase-warning").hidden = true; $("#recovery-confirm").checked = false; $("#recovery-done").disabled = true; });
+$("#recovery-dialog").addEventListener("close", () => {
+ $("#recovery-phrase").textContent = "";
+ $("#recovery-passphrase-warning").hidden = true;
+ $("#recovery-confirm").checked = false;
+ $("#recovery-done").disabled = true;
+ if (state.pendingCreatedWalletId) {
+ $("#wallet-select").value = state.pendingCreatedWalletId;
+ state.pendingCreatedWalletId = "";
+ toast(state.language === "zh" ? "钱包已加密保存;请在上方重新输入密码并连接" : "Wallet encrypted and saved. Re-enter its password above to connect", "good");
+ $("#wallet-password").focus();
+ }
+});
$("#wallet-dialog").addEventListener("close", clearWalletSecrets);
$("#build-draft").addEventListener("click", buildAndBroadcast);
$("#external-covenant-inspect").addEventListener("click", inspectExternalCovenant);
diff --git a/templates/commit-reveal/manifest.json b/templates/commit-reveal/manifest.json
index f56c0c5..9dac131 100644
--- a/templates/commit-reveal/manifest.json
+++ b/templates/commit-reveal/manifest.json
@@ -7,7 +7,7 @@
"descriptionEn": "Deployment fixes a domain and commitment; the recipient reveals payload and salt to claim, or the sender refunds after timeout. TN10 experimental only.",
"experimentalOnly": true,
"networkAllowlist": ["tn10"],
- "compilerProfileId": "latest-4b0e1cd",
+ "compilerProfileId": "latest-cb34aa5",
"requirementsEn": "Release a whole escrow only when a domain-separated payload commitment is revealed, with an authenticated timeout refund.",
"risk": "experimental",
"sourceFile": "contract.sil",
diff --git a/templates/kcc721-experimental/manifest.json b/templates/kcc721-experimental/manifest.json
index 9d22868..88ef487 100644
--- a/templates/kcc721-experimental/manifest.json
+++ b/templates/kcc721-experimental/manifest.json
@@ -7,7 +7,7 @@
"descriptionEn": "Guided Collection metadata, NFT identity and owner setup with a locally computed digest. Four covenants are included for TN10 research; ordinary deployment remains disabled.",
"experimentalOnly": true,
"networkAllowlist": ["tn10"],
- "compilerProfileId": "latest-4b0e1cd",
+ "compilerProfileId": "latest-cb34aa5",
"deploymentMode": "pack-only",
"deploymentBlockedReason": "KCC721 is a four-contract TN10 experimental pack; its dedicated independently reviewed genesis/mint builder is not included yet, so standalone deployment is disabled",
"requirementsEn": "Research UTXO-native NFTs whose immutable identity and current owner are enforced by a live covenant lineage.",
diff --git a/templates/merkle-one-time-claim/manifest.json b/templates/merkle-one-time-claim/manifest.json
index 4448080..19a7f60 100644
--- a/templates/merkle-one-time-claim/manifest.json
+++ b/templates/merkle-one-time-claim/manifest.json
@@ -7,7 +7,7 @@
"descriptionEn": "A claimant consumes the UTXO once with a committed Merkle proof and salt; the refund wallet recovers it after timeout. TN10 experimental only.",
"experimentalOnly": true,
"networkAllowlist": ["tn10"],
- "compilerProfileId": "latest-4b0e1cd",
+ "compilerProfileId": "latest-cb34aa5",
"requirementsEn": "Allow exactly one claimant to consume a Merkle-committed claim before a timeout, with a signed refund path afterwards.",
"risk": "experimental",
"sourceFile": "contract.sil",
diff --git a/test/studio.test.mjs b/test/studio.test.mjs
index a072aa7..d650c49 100644
--- a/test/studio.test.mjs
+++ b/test/studio.test.mjs
@@ -319,6 +319,18 @@ test("human template fields deterministically produce compile-ready constructor
}
});
+test("template project names follow the requested UI language", () => {
+ const templates = new TemplateStore();
+ const template = templates.get("inheritance-vault");
+ const parameters = configuredTemplateParameters(template);
+ const zh = templates.projectInput(template.id, "tn10", parameters, { language: "zh" });
+ const en = templates.projectInput(template.id, "tn10", parameters, { language: "en" });
+ assert.equal(zh.name, template.titleZh);
+ assert.equal(zh.specification.title, template.titleZh);
+ assert.equal(en.name, template.titleEn);
+ assert.equal(en.specification.title, template.titleEn);
+});
+
test("template fields reject wrong-network and duplicate authorization wallets", () => {
const templates = new TemplateStore();
const owner = templates.get("owner-vault");
@@ -636,25 +648,48 @@ contract Compatibility(pubkey owner) {
}
}`;
const profiles = compilerProfiles();
- assert.deepEqual(profiles.map((profile) => profile.id), ["latest-4b0e1cd", "legacy-2a3961c"]);
+ assert.deepEqual(profiles.map((profile) => profile.id), ["latest-cb34aa5", "legacy-2a3961c"]);
assert.ok(profiles.every((profile) => profile.configured));
- const report = detectBreakingChanges(`${legacySource}\n// checkSigFromStack and tx.inputs[0].outpointTransactionHash.reverse()`, "latest-4b0e1cd");
+ const report = detectBreakingChanges(`${legacySource}\n// checkSigFromStack and tx.inputs[0].outpointTransactionHash.reverse()`, "latest-cb34aa5");
assert.equal(report.compatible, false);
assert.ok(report.findings.some((finding) => finding.id === "entry-syntax" && finding.line === 3));
assert.ok(report.findings.some((finding) => finding.id === "reverse-removed" && finding.replacement === null));
- const migrated = migrateSourceToProfile(legacySource, "latest-4b0e1cd");
+ const migrated = migrateSourceToProfile(legacySource, "latest-cb34aa5");
assert.deepEqual(migrated.applied, ["entry-syntax"]);
assert.equal(migrated.report.compatible, true);
const owner = byteArray(new Uint8Array(32).fill(3));
const legacyArtifact = await compileContract({ source: legacySource, constructorArgs: [owner], compilerProfileId: "legacy-2a3961c" });
- const latestArtifact = await compileContract({ source: migrated.source, constructorArgs: [owner], compilerProfileId: "latest-4b0e1cd" });
+ const latestArtifact = await compileContract({ source: migrated.source, constructorArgs: [owner], compilerProfileId: "latest-cb34aa5" });
assert.equal(legacyArtifact.compiler.artifactBytecodeField, "script");
assert.equal(latestArtifact.compiler.artifactBytecodeField, "bytecode");
- const encoded = encodeConstructorArgsForProfile(migrated.source, [owner], "latest-4b0e1cd");
+ const encoded = encodeConstructorArgsForProfile(migrated.source, [owner], "latest-cb34aa5");
assert.equal(encoded[0].data.type_ref.base, "byte");
assert.deepEqual(encoded[0].data.type_ref.array_dims, [{ kind: "fixed", value: 32 }]);
});
+test("latest compiler profile detects and enforces cb34aa5 hardening changes", async () => {
+ const duplicate = `pragma silverscript ^0.1.0;
+contract Duplicate() {
+ function same() { require(true); }
+ entry same() { require(true); }
+}`;
+ const shadow = `pragma silverscript ^0.1.0;
+contract Shadow() {
+ int field = 1;
+ entry spend(int field) { require(field > 0); }
+}`;
+ const ordered = `pragma silverscript ^0.1.0;
+contract Ordered() {
+ entry spend() { require("b" > "a"); }
+}`;
+ assert.ok(detectBreakingChanges(duplicate, "latest-cb34aa5").findings.some((finding) => finding.id === "duplicate-function-name"));
+ assert.ok(detectBreakingChanges(shadow, "latest-cb34aa5").findings.some((finding) => finding.id === "entry-parameter-shadows-field"));
+ assert.ok(detectBreakingChanges(ordered, "latest-cb34aa5").findings.some((finding) => finding.id === "ordered-comparison-numeric-only"));
+ await assert.rejects(compileContract({ source: duplicate, compilerProfileId: "latest-cb34aa5" }), /duplicate function name/i);
+ await assert.rejects(compileContract({ source: shadow, compilerProfileId: "latest-cb34aa5" }), /conflicts with contract field/i);
+ await assert.rejects(compileContract({ source: ordered, compilerProfileId: "latest-cb34aa5" }), /ordered comparison requires numeric operands/i);
+});
+
test("CovenantStateSource rejects false matches, records fallback and fails on ambiguity", async () => {
const request = { covenantId: "11".repeat(32), script: "aa55" };
const valid = (txByte, amount = 10_000n) => ({
@@ -949,7 +984,7 @@ test("TN10 Experimental KCC721 pack compiles all pinned contracts and blocks sta
for (const contract of pack.packContracts) {
const artifact = await compileContract({ source: contract.source, constructorArgs: contract.constructorArgs, compilerProfileId: pack.compilerProfileId });
assert.ok(artifact.programHex.length > 0, contract.id);
- assert.equal(artifact.compiler.id, "latest-4b0e1cd");
+ assert.equal(artifact.compiler.id, "latest-cb34aa5");
compiled.set(contract.id, artifact);
}
const configured = templates.projectInput(pack.id, "tn10", configuredTemplateParameters(pack));
@@ -1197,6 +1232,12 @@ contract Risky() {
}
}`);
assert.deepEqual(risky.findings.map((finding) => finding.code), ["SS001", "SS002", "SS003", "SS004"]);
+ const sameInputScript = await staticAnalyze(`contract Continuation() {
+ entry spend() {
+ require(tx.outputs[0].scriptPubKey == tx.inputs[this.activeInputIndex].scriptPubKey);
+ }
+ }`);
+ assert.equal(sameInputScript.findings.some((finding) => finding.code === "SS002"), false);
});
test("deployment builder rejects source edited after compilation before network access", async () => {