Skip to content

fix: accept SOption(T) where T expected in check_post_eval_tpe#860

Open
mwaddip wants to merge 3 commits into
ergoplatform:developfrom
mwaddip:fix/parse-time-option-leniency
Open

fix: accept SOption(T) where T expected in check_post_eval_tpe#860
mwaddip wants to merge 3 commits into
ergoplatform:developfrom
mwaddip:fix/parse-time-option-leniency

Conversation

@mwaddip
Copy link
Copy Markdown

@mwaddip mwaddip commented Apr 16, 2026

Widens Expr::check_post_eval_tpe to accept SOption(T) where T is expected — matches JVM OneArgumentOperationSerializer.parse's unchecked asValue[T] cast. Without this, sigma-rust rejects mainnet block 1,711,120 tx dce9…ff2c (uses blake2b256(getVarColl[Byte]) without .get).

Eval-time type check unchanged — UnexpectedValue still fires at runtime if the var is missing.

mwaddip and others added 3 commits April 16, 2026 15:14
Pin two ergoTree fixtures from output[0] and output[2] of tx
dce90de114a7d36cb2dd724c04ccc11b461a02bd2a783ef820234046cb47ff2c
in mainnet block 1,711,120. output[0] reproduces the
InvalidExprEvalTypeError "expected SColl(SByte), got SOption(SColl(SByte))"
that blocks ergo-node-rust v0.3.1 from advancing past this height. The
script uses blake2b256(getVar[Coll[Byte]](1)) without an explicit .get
unwrap; the JVM v6.0.3 reference parses it without complaint because
OneArgumentOperationSerializer uses an unchecked asValue[T] cast at
parse time.

output[2] is added at the same time as a paired round-trip check —
already passes today, but locks in protection against future
regressions that break only one of the two boxes.

The output[0] test is expected to fail with this commit; the
following commit makes it pass.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
JVM v6.0.3 sigmastate-interpreter never type-checks at parse time —
OneArgumentOperationSerializer uses asValue[T] (an unchecked cast) and
the predef Lambda arms it dispatches into perform no validation. Real
on-chain scripts pass getVar[T](id) (post-eval SOption(T)) directly
into combinators that statically expect T (e.g. blake2b256). Without
this leniency sigma-rust rejects txs the JVM accepts, breaking
consensus.

Mainnet block 1,711,120 tx[1] output[0] is the observed reproducer:
script computes blake2b256(getVar[Coll[Byte]](1)) without .get and the
parser bails inside CalcBlake2b256::try_build with
"InvalidExprEvalTypeError: expected SColl(SByte), got
SOption(SColl(SByte))". The widened predicate accepts the Option
wrapping at parse time; evaluation-time behavior is unchanged
(evaluator's UnexpectedValue arm still fires if the script actually
runs, matching JVM runtime).

Same widening covers identical-shape failures from CalcSha256,
ByteArrayToBigInt/Long, DecodePoint, Extract*, LongToByteArray,
SigmaPropBytes, LogicalNot, CreateProveDlog, SubstConstants,
CreateProveDhTuple, CreateAvlTree, TreeLookup, OptionGetOrElse —
all 27 check_post_eval_tpe call sites in the workspace.

Regression test (added in prior commit) now passes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The crate has #![deny(clippy::expect_used)] and the surrounding tests
module only allows clippy::unwrap_used and clippy::panic. Switch the
two new regression tests to .unwrap() to match the existing convention
in this file. No semantic change — both helpers still panic with the
underlying parse error on failure.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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