Skip to content

feat(api): /script/p2sAddress + /script/p2shAddress compile routes (M6) - #165

Merged
arkadianet merged 4 commits into
mainfrom
feat/ergoscript-m6-script-rest
Jul 7, 2026
Merged

feat(api): /script/p2sAddress + /script/p2shAddress compile routes (M6)#165
arkadianet merged 4 commits into
mainfrom
feat/ergoscript-m6-script-rest

Conversation

@arkadianet

@arkadianet arkadianet commented Jul 7, 2026

Copy link
Copy Markdown
Owner

What this is

The M6 follow-up to #164: wires the merged byte-parity compiler into the node's REST surface as the two compile-requiring /script/* endpoints the Scala node has and this node previously omitted — POST /script/p2sAddress and POST /script/p2shAddress — plus the DoS hardening the REST exposure requires.

Three commits:

  1. parse.rs depth guard (ParseError::TooDeep, cap 128 > ergo-ser's consensus MAX_EXPR_DEPTH=110) — source-text depth upper-bounds every downstream structure, so one cap at parse covers the whole pipeline. Audited all six transform passes: call-stack-recursive but none deepens beyond source depth. Deepest real corpus contract still parses.
  2. EnvValue::ProveDlog([u8; 33]) — the one missing env entry point for a real, emittable SigmaProp constant (mirrors the GroupElement arm: on-curve check, ConstPayload::ProveDlog, SType::SSigmaProp). Downstream (binder/typer/emit) already supported it via PK(...).
  3. ergo-api/src/script.rs — the two handlers on a Router<(NetworkPrefix, Arc<dyn WalletAdmin>)> tuple-state sub-router; wallet-addresses → myPubKey_N env (.take(100) = Scala's loadMaxKeys); every compile failure → the standard 400 bad_request envelope (Scala parity: one BadRequest for all phases). Route tests with a stub WalletAdmin + committed oracle vectors.

Security note (deliberate, operator-approved)

Scala's ScriptApiRoute carries no auth, so these routes are public for parity — even though the server-side env bakes wallet pubkeys into the returned P2S address (decodable back to those pubkeys). This was reviewed and explicitly accepted to mirror Scala rather than gate behind api_key; an operator who wants them gated can front them at the proxy. (The native v1 API design keeps wallet-keyed compile OUT of the public surface — this compat behavior stays frozen here only.)

Test plan

cargo fmt --all -- --check
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test --workspace     # 5665 passed / 0 failed on the rebased branch

Route tests: ergo-api/tests/script_compile_routes.rs (envelope, tier, empty-wallet, error mapping). Compile parity itself is guarded by the merged #164 gate (110/110).

🤖 Generated with Claude Code

https://claude.ai/code/session_01BUh2DBnAPqThdYFZW5D8wx

Summary by CodeRabbit

  • New Features

    • Added new script compilation endpoints for generating p2s and p2sh addresses from submitted script source.
    • The API now accepts a script source and tree version, returning the compiled address result.
    • Wallet-backed address information can now be used automatically during compilation.
  • Bug Fixes

    • Added stricter validation and clearer error responses for invalid scripts, missing wallet data, and overly complex inputs.

arkadianet added 3 commits July 7, 2026 23:24
…EST surface)

M6 recon §5 flagged parse.rs's expr()/type_() recursive descent as
recursion-unbounded: fine for trusted CLI/test input, a stack-overflow
DoS surface once the /script/p2sAddress + /script/p2shAddress REST
routes expose it to untrusted source text. Add a single Cursor::depth
counter shared by both expr() and type_() (each wrapped so every
recursive call in the file is guarded, not just the top-level entry),
capped at MAX_PARSE_DEPTH = 128 — conservative and non-oracle-pinned
(the compiler's own limits are explicitly not consensus-critical),
set above ergo-ser's consensus MAX_EXPR_DEPTH = 110.

Recursion audit of the M4/M5 transform passes (fold/lower/inline/cse/
isproven/tuple.rs): all six are call-stack-recursive 1:1 tree walks
over the already-depth-bounded typed AST (no Vec-based explicit
stack anywhere), but none amplifies depth beyond what the parser
already accepted, so the single parse-time cap transitively bounds
them too — no independent per-module guards needed.

Also removes an untracked, explicitly "do NOT commit" scratch probe
binary (ergo-compiler/examples/probe.rs) that was failing
`cargo clippy --all-targets` (clone_on_copy on GroupElement) and
blocking the gate.
D-E3 documented the gap: the only pre-M6 SigmaProp-family env constant
(EnvValue::SigmaProp(String)) is an opaque label emit rejects with
UnsupportedNode — no env entry point produced a REAL, emittable
SigmaProp. Scala's ScriptApiRoute.keysToEnv injects each wallet
ProveDlog pubkey as myPubKey_N for /script/p2sAddress + p2shAddress,
so M6 needs one.

Add EnvValue::ProveDlog([u8; 33]) + its lift arm: on-curve check via
the same decompress_to_affine_hex path as GroupElement (D-T5 policy —
rejects off-curve AND identity), producing ConstPayload::ProveDlog +
SType::SSigmaProp — the identical shape binder.rs's PK(...) rule
already produces, so binder/typer/emit need no other change. Ledgered
as D-E4 (distinct from, not a fix to, D-E3's still-opaque SigmaProp
label).
The two compile-requiring members of Scala's ScriptApiRoute this node
previously omitted (utils.rs served only the decode-only addressToTree
/ addressToBytes members — "this node ships no compiler" is no longer
true after M1-M6). Wires ergo_compiler::compile up through a new
ergo-api/src/script.rs module: CompileRequestDto {source, treeVersion},
a wallet-address -> ScriptEnv builder mirroring Scala's keysToEnv
(myPubKey_N -> ProveDlog(pk), capped at loadMaxKeys=100), and two
handlers reading CompileResult.p2s_address / .p2sh_address into
{"address": ...}.

Mounted as a NEW Router<(NetworkPrefix, Arc<dyn WalletAdmin>)>
sub-router beside utils_routes (server.rs), mirroring the
miner_stats_routes tuple-state precedent. PUBLIC/ungated, matching
Scala's ScriptApiRoute (no withAuth) — even though it reads wallet
pubkeys server-side, same as the two existing decode-only /script/*
routes. Rewrites the now-false "ships no compiler" doc comments in
utils.rs.

An empty or errored wallet-address read degrades to an empty env
(a keyless script still compiles) rather than a 500 — the real
ergo-node WalletAdmin::addresses() backend never actually returns Err
regardless of lock state, confirmed by reading
ergo-node/src/node/wallet_bridge/commands/admin.rs; a genuine Err from
an alternate WalletAdmin implementation is surfaced honestly (Locked/
Uninitialized -> 400, else 500) rather than invented.

Tests: script.rs unit tests for the env builder (injection, cap,
empty-list, invalid-address); ergo-api/tests/script_compile_routes.rs
end-to-end route tests (self-consistent against direct
ergo_compiler::compile calls — no live Scala node needed) covering the
happy path, wallet-pubkey env injection, keyless-on-empty-wallet,
compile-error 400 mapping, and reachability/no-auth on the REAL
merged router via router_with_wallet.
@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@arkadianet, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 46 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 20ba00d7-2112-4953-bf94-f5bcb4c13c56

📥 Commits

Reviewing files that changed from the base of the PR and between 85337e7 and 866898b.

📒 Files selected for processing (3)
  • ergo-api/src/script.rs
  • ergo-api/tests/script_compile_routes.rs
  • ergo-compiler/src/parse.rs
📝 Walkthrough

Walkthrough

This PR adds compile-requiring script endpoints (/script/p2sAddress, /script/p2shAddress) to ergo-api, backed by ergo-compiler, along with wallet-address-derived environment injection. Separately, ergo-compiler gains an EnvValue::ProveDlog constant variant and a shared parser recursion depth guard (ParseError::TooDeep).

Changes

Compile-requiring script API routes

Layer / File(s) Summary
Request DTO and env construction
ergo-api/Cargo.toml, ergo-api/src/lib.rs, ergo-api/src/script.rs
Adds ergo-compiler dependency, exports the new script module, defines CompileRequestDto, and adds build_env to inject wallet P2PK pubkeys as ProveDlog entries capped at MAX_ENV_KEYS, with unit tests.
Compile pipeline and route handlers
ergo-api/src/script.rs
Adds wallet error mapping, the shared compile_address_response pipeline, and p2s_address_handler/p2sh_address_handler.
Router wiring and docs
ergo-api/src/server.rs, ergo-api/src/utils.rs
Mounts a new public script_routes sub-router with tuple state, and updates surrounding documentation.
End-to-end route tests
ergo-api/tests/script_compile_routes.rs
Adds a stub wallet admin, router helpers, and happy-path, error-path, and public-access tests for the new routes.

Compiler ProveDlog env value and parser depth guard

Layer / File(s) Summary
ProveDlog env value lifting
ergo-compiler/src/env.rs, ergo-compiler/src/lib.rs
Adds EnvValue::ProveDlog([u8; 33]), lift-time on-curve/identity validation, resulting ConstPayload::ProveDlog constant, deviation ledger entry, and tests.
Parser recursion depth guard
ergo-compiler/src/error.rs, ergo-compiler/src/parse.rs
Adds ParseError::TooDeep, MAX_PARSE_DEPTH, Cursor::depth tracking, guarded wrappers around expr/type_, clamp passthrough, and boundary/regression tests.

Estimated code review effort: 3 (Moderate) | ~30 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant p2s_address_handler
  participant compile_address_response
  participant WalletAdmin
  participant ergo_compiler

  Client->>p2s_address_handler: POST /script/p2sAddress (source, treeVersion)
  p2s_address_handler->>compile_address_response: forward request
  compile_address_response->>WalletAdmin: addresses()
  WalletAdmin-->>compile_address_response: tracked addresses
  compile_address_response->>compile_address_response: build_env (myPubKey_N -> ProveDlog)
  compile_address_response->>ergo_compiler: compile(source, env, tree_version)
  ergo_compiler-->>compile_address_response: compiled result
  compile_address_response-->>Client: JSON { p2s_address }
Loading

Possibly related PRs

  • arkadianet/ergo#62: Introduces/changes the decode_p2pk_address logic used by the new script handlers to decode wallet addresses into P2PK pubkeys.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and accurately summarizes the main change: adding the /script/p2sAddress and /script/p2shAddress compile routes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/ergoscript-m6-script-rest

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
ergo-compiler/src/env.rs (1)

200-213: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider extracting shared on-curve validation.

The GroupElement (Lines 184-194) and ProveDlog arms perform an identical validate-then-wrap pattern via decompress_to_affine_hex. A small helper (e.g. fn validated_curve_bytes(bytes: &[u8;33]) -> Result<[u8;33], GroupElementError>) would remove the duplication, but with only two call sites the benefit is marginal.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ergo-compiler/src/env.rs` around lines 200 - 213, The GroupElement and
ProveDlog branches in EnvValue both repeat the same validate-then-wrap logic
with decompress_to_affine_hex, so extract that shared on-curve check into a
small helper (for example, a validated_curve_bytes-style function) and have both
arms call it before constructing the TypedExpr::Constant payload. Keep the
existing rejection behavior and return types aligned with the current
GroupElement and ProveDlog paths so the helper centralizes validation without
changing semantics.
ergo-compiler/src/parse.rs (1)

468-479: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider extracting the duplicated depth-guard wrapper.

type_/type_impl and expr/expr_impl implement the identical increment-check-decrement pattern. A shared helper would prevent the two guards from drifting if the logic ever changes (e.g., adding telemetry, adjusting the error position captured).

♻️ Proposed shared helper
+fn with_depth_guard<T>(
+    c: &mut Cursor,
+    f: impl FnOnce(&mut Cursor) -> Result<T, ParseError>,
+) -> Result<T, ParseError> {
+    c.depth += 1;
+    if c.depth > MAX_PARSE_DEPTH {
+        let pos = c.peek().start;
+        let depth = c.depth;
+        c.depth -= 1;
+        return Err(ParseError::TooDeep { pos, depth });
+    }
+    let result = f(c);
+    c.depth -= 1;
+    result
+}
+
 fn type_(c: &mut Cursor, raw_entry: bool) -> Result<SType, ParseError> {
-    c.depth += 1;
-    if c.depth > MAX_PARSE_DEPTH {
-        let pos = c.peek().start;
-        let depth = c.depth;
-        c.depth -= 1;
-        return Err(ParseError::TooDeep { pos, depth });
-    }
-    let result = type_impl(c, raw_entry);
-    c.depth -= 1;
-    result
+    with_depth_guard(c, |c| type_impl(c, raw_entry))
 }

Also applies to: 959-970

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ergo-compiler/src/parse.rs` around lines 468 - 479, Extract the repeated
depth-guard pattern used by type_/type_impl and expr/expr_impl into a shared
helper so the increment, MAX_PARSE_DEPTH check, error capture, and decrement
live in one place. Update type_ and expr to delegate their depth handling to
that helper while still calling type_impl and expr_impl for the actual parsing
work. Keep the helper generic enough to preserve the existing
ParseError::TooDeep behavior and to avoid the two wrappers drifting if the depth
logic changes later.
ergo-api/src/script.rs (1)

84-99: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

No test exercises the wallet_error_response Locked/Uninitialized/Other branches.

Both the unit tests here and ergo-api/tests/script_compile_routes.rs's StubAdmin::addresses() always return Ok, so the 400-vs-500 mapping logic in wallet_error_response is never actually run by any test.

♻️ Suggested addition
#[test]
fn wallet_error_response_locked_maps_to_bad_request() {
    let resp = wallet_error_response(WalletAdminError::Locked);
    assert_eq!(resp.status(), axum::http::StatusCode::BAD_REQUEST);
}

#[test]
fn wallet_error_response_other_maps_to_internal_error() {
    // pick a non-Locked/Uninitialized variant
    let resp = wallet_error_response(WalletAdminError::/* other variant */);
    assert_eq!(resp.status(), axum::http::StatusCode::INTERNAL_SERVER_ERROR);
}

Also applies to: 142-187

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ergo-api/src/script.rs` around lines 84 - 99, Add test coverage for the
wallet_error_response mapping logic in script.rs, since the current tests only
exercise Ok paths and never verify the Locked/Uninitialized versus other error
branches. Create focused unit tests for wallet_error_response that pass
WalletAdminError::Locked, WalletAdminError::Uninitialized, and at least one
non-ready variant to assert BAD_REQUEST vs INTERNAL_SERVER_ERROR. Also update
the script_compile_routes.rs StubAdmin::addresses() test path so it can return
an error and drive those branches through the route layer, ensuring the status
mapping is actually exercised by tests.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@ergo-api/src/script.rs`:
- Around line 84-99: Add test coverage for the wallet_error_response mapping
logic in script.rs, since the current tests only exercise Ok paths and never
verify the Locked/Uninitialized versus other error branches. Create focused unit
tests for wallet_error_response that pass WalletAdminError::Locked,
WalletAdminError::Uninitialized, and at least one non-ready variant to assert
BAD_REQUEST vs INTERNAL_SERVER_ERROR. Also update the script_compile_routes.rs
StubAdmin::addresses() test path so it can return an error and drive those
branches through the route layer, ensuring the status mapping is actually
exercised by tests.

In `@ergo-compiler/src/env.rs`:
- Around line 200-213: The GroupElement and ProveDlog branches in EnvValue both
repeat the same validate-then-wrap logic with decompress_to_affine_hex, so
extract that shared on-curve check into a small helper (for example, a
validated_curve_bytes-style function) and have both arms call it before
constructing the TypedExpr::Constant payload. Keep the existing rejection
behavior and return types aligned with the current GroupElement and ProveDlog
paths so the helper centralizes validation without changing semantics.

In `@ergo-compiler/src/parse.rs`:
- Around line 468-479: Extract the repeated depth-guard pattern used by
type_/type_impl and expr/expr_impl into a shared helper so the increment,
MAX_PARSE_DEPTH check, error capture, and decrement live in one place. Update
type_ and expr to delegate their depth handling to that helper while still
calling type_impl and expr_impl for the actual parsing work. Keep the helper
generic enough to preserve the existing ParseError::TooDeep behavior and to
avoid the two wrappers drifting if the depth logic changes later.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1e16f662-b04d-4198-9c75-d5c600ba4f0e

📥 Commits

Reviewing files that changed from the base of the PR and between ada14b8 and 85337e7.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (10)
  • ergo-api/Cargo.toml
  • ergo-api/src/lib.rs
  • ergo-api/src/script.rs
  • ergo-api/src/server.rs
  • ergo-api/src/utils.rs
  • ergo-api/tests/script_compile_routes.rs
  • ergo-compiler/src/env.rs
  • ergo-compiler/src/error.rs
  • ergo-compiler/src/lib.rs
  • ergo-compiler/src/parse.rs

- parse.rs: extract the duplicated increment/check/decrement depth-guard into
  a shared with_depth_guard helper (type_/expr_ can no longer drift); all four
  depth-guard tests unchanged and green.
- script.rs: cover the wallet_error_response mapping — unit tests for
  Locked/Uninitialized→400 and Internal→500, plus a route-level test driving
  a Locked wallet through /script/p2sAddress (400 + bad-request envelope,
  never a 500). StubAdmin gains a fail_locked switch.
- env.rs helper extraction SKIPPED: two call sites, CodeRabbit's own grading
  calls the benefit marginal — the duplication is a straight-line
  validate-then-wrap with no drift hazard.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BUh2DBnAPqThdYFZW5D8xw
@arkadianet

Copy link
Copy Markdown
Owner Author

Addressed the review:

  • parse.rs depth-guard duplication — fixed: extracted with_depth_guard; type_/expr now share one guard (all 4 depth tests green unchanged).
  • script.rs untested error branches — fixed: unit tests for wallet_error_response (Locked/Uninitialized → 400, Internal → 500) + a route-level test driving a Locked wallet through /script/p2sAddress (asserts 400 + the bad-request envelope, and the wallet not ready detail).
  • env.rs shared on-curve helper — skipped: two call sites and (per the review's own grading) marginal benefit; the duplicated fragment is a straight-line validate-then-wrap with no drift hazard. Happy to extract if it grows a third caller.

@arkadianet
arkadianet merged commit 39cb0d6 into main Jul 7, 2026
8 of 9 checks passed
@arkadianet
arkadianet deleted the feat/ergoscript-m6-script-rest branch July 7, 2026 13:47
arkadianet pushed a commit that referenced this pull request Jul 7, 2026
…ain)

#165 (M6) merged to main added ParseError::TooDeep; rebasing M7 onto current
main makes shift_err's match non-exhaustive. Shift TooDeep's pos like the other
arms.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BUh2DBnAPqThdYFZW5D8wx
arkadianet added a commit that referenced this pull request Jul 7, 2026
#166)

* feat(compiler): M7 ContractParser + ContractTemplate assembly

Adds the @contract template layer above the expression grammar, mirroring
Scala's sigmastate.lang.ContractParser being a sibling of SigmaParser
(sigma-state 6.0.2), plus the SigmaTemplateCompiler.compile/assemble pipeline.

Why: contracts declare doc metadata + named parameters that compile to
segregated ConstantPlaceholder slots; this is the parser/metadata layer M1
deliberately deferred. Reuses parse()/parse_type()/typer/graph-build verbatim
so the body grammar and compile pipeline are never duplicated.

- contract_parse.rs: Docs + Signature sub-parsers + parse_contract; new AST
  (ContractDoc/ParameterDoc/ContractParam/ContractSignature/
  ParsedContractTemplate). Type reuses parse_type; body reuses parse; default
  is a restricted literal-only parse. Faithful @returns-with-text /
  non-star-line / non-literal-default rejects.
- typecheck.rs: typecheck_contract_body threads a name->SType param env into
  the existing typer env-lookup path (empty ScriptEnv, no new typer logic).
- emit.rs: Scope gains a placeholder env; a param identifier emits
  ConstantPlaceholder(index) (opcode 0x73). Empty on the normal path.
- tree.rs: extract the graph-building pipeline into graph_build(), shared by
  compile() and the contract assembler so both stay byte-identical.
- contract_template.rs: ContractTemplate + compile_contract; declaration-order
  placeholder indices for <=4 params, canonical ContractTemplate.serializer
  wire form. >=5 params return ContractError::TooManyParamsForOrdering
  (TODO(M7-hashmap-order)) rather than a silently-wrong tree.
- token.rs: expose is_id_start/is_id_char to the crate.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BUh2DBnAPqThdYFZW5D8wx

* test(compiler): M7 ct oracle verb + contract corpus + byte-exact parity gate

Why: grade the assembled ContractTemplate against the real Scala reference so
the <=4-param declaration-order claim is proven, not asserted.

- TyperOracle.scala: new `ct` verb runs SigmaTemplateCompiler(NET).compile and
  prints ContractTemplate.serializer.toBytes as hex.
- test-vectors/ergoscript/contract/: 12 hand-authored sources + committed
  contract_seed.json (verbatim oracle captures, sigma-state 6.0.2 / Scala
  2.12.21, tree_version=3, testnet).
- contract_template_parity.rs: oracle OK (<=4 params) => byte-identical
  serialize(); oracle OK (>=5 params) => our deliberate
  TooManyParamsForOrdering deferral; oracle REJECT => we reject.

Result: 10 <=4-param vectors byte-exact vs the oracle; the 5+ HashMap-order
port is cleanly deferred (flagged, never mis-emitted).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BUh2DBnAPqThdYFZW5D8wx

* chore: remove committed sdd report from tree (gitignored scratch, local-path leak)

.superpowers/ is session scratch — reports never ship in the public tree
(and this one carried local absolute paths).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BUh2DBnAPqThdYFZW5D8wx

* fix(compiler): M7 ContractParser CodeRabbit findings — string-literal delimiters + dup-param reject

Three findings on PR #166's @contract ContractParser, each verified against the
sigma-state 6.0.2 reference and the `ct` JVM oracle before fixing.

Finding 1 (Major) — string-literal-aware delimiters. Scala's `param.rep(1, ",")`
and `params = "(" ~ ... ~ ")"` read each `ExprLiteral` with the real
`Literals.String` grammar (\"-escape aware), so a `,`/`)` inside a string-literal
default is content, not structural. Oracle ACCEPTs `s: String = "a,b"`,
`= ")"`, `= "\""`, and a multi-param mix; the Rust splitter/paren-matcher were
byte-blind and rejected them (reject-valid gap). Fixed by tracking string/escape
state (StrScan) in both the top-level comma splitter and the closing-`)` matcher.
All four now compile byte-identical to the oracle (new corpus vectors).

Finding 2 (Major) — duplicate parameter names. Scala does NOT last-wins:
`ContractTemplate.validate()` runs `require(!paramNames.contains(p.name), ...)`
and throws IllegalArgumentException. Oracle REJECTs `def f(a: Int, a: Long)`.
The Rust path silently accepted it (two Parameters sharing a name). Added a
post-typecheck uniqueness check mirroring the Scala require
(ContractError::DuplicateParamName).

Finding 3 (Minor) — param-type parse errors reported positions relative to the
sliced type string, not source coordinates (the body path already used
shift_err). Shifted param-type `parse_type` errors by the same offset
(base + colon + 1).

Corpus stays byte-exact (5 new vectors: 4 byte-exact ACCEPT, 1 REJECT). Gate
green: fmt/clippy -D warnings/test --workspace.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BUh2DBnAPqThdYFZW5D8wx

* fix(compiler): cover ParseError::TooDeep in shift_err (M6 merged to main)

#165 (M6) merged to main added ParseError::TooDeep; rebasing M7 onto current
main makes shift_err's match non-exhaustive. Shift TooDeep's pos like the other
arms.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BUh2DBnAPqThdYFZW5D8wx

---------

Co-authored-by: arkadianet <rkadias@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
arkadianet pushed a commit that referenced this pull request Jul 13, 2026
Bump workspace version to 0.5.2 and promote the changelog: the complete
v1 product API (#168-#185, #188), shadow validation as a production mode
(#193-#195), the operator observability wave (#187, #190, #192, #194),
two live accept-invalid consensus fixes (#176, #179), ErgoScript
compiler byte-parity completion (#165-#167, #175), and the #160-#163
sync/recovery fixes. Full workspace gate run on the merge result.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BUh2DBnAPqThdYFZW5D8wx
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant