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 () => {