Renumber test_transpiler_clamping.sh tests from 5-14 to 6-15 to avoid
overlap with test_inject_extraction.sh Test 5 (#1017).
Items #1012 (ts-node→tsx) and #986 (CI using npm test) were already
resolved by prior commits.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fixes#618
## Changes
Add stack depth validation in processExecIf() so asymmetric EXEC.IF branches (where one branch pushes more values than the other) throw an explicit error instead of silently padding with '0'. Error messages identify both branch depths for DYADIC and BOOLEAN stacks. Removed dead-code '0'/'false' fallbacks in buildAssignments and reconstruction. Updated existing unbalanced-branch tests to expect errors; added regression tests for error message content and BOOLEAN mismatch. All existing seed files (optimizer_v3.push3, optimizer_seed.push3) continue to transpile.
Co-authored-by: openhands <openhands@all-hands.dev>
Reviewed-on: https://codeberg.org/johba/harb/pulls/1033
Reviewed-by: Disinto_bot <disinto_bot@noreply.codeberg.org>
Add TypeScript unit test suite for the Push3 transpiler using Node's
built-in test runner (node:test) with tsx. 47 tests across 12 suites
covering parser, stack underflow/overflow, EXEC.IF balanced/unbalanced/
nested branching, arithmetic, boolean ops, name binding, and integration.
Update CI to run `npm test` (which now includes unit tests + existing
bash tests) and scope transpiler-tests step to only trigger on changes
to tools/push3-transpiler/**.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add assertUint256Max1e18 validator in index.ts and apply it to the ci,
anchorShare, and discoveryDepth output literals. Programs emitting values
> 1e18 for these fields now fail with a clear transpiler-level error instead
of silently violating LiquidityManager invariants at runtime.
Add tests 12-14 in test_transpiler_clamping.sh covering the over-range
rejection for each of the three fields.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The 30-way threshold lookup in optimizer_seed.push3 generates enough
local variables to trigger "Stack too deep" without IR compilation.
Add via_ir = true to the minimal foundry.toml created in both test
scripts, matching the setting in onchain/foundry.toml.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- test_transpiler_clamping.sh: add Test 11 that runs forge build on the
valid Solidity output from Test 6; fails if the transpiled contract
does not compile (regression guard for #900)
- test_inject_extraction.sh: add SCRIPT_DIR, then Test 5 that transpiles
optimizer_seed.push3 and runs forge build on the generated contract;
ensures the full push3→Solidity→compile pipeline stays green
- .woodpecker/ci.yml: add transpiler-tests step that installs npm deps
and runs both test scripts with forge on PATH
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Clamp anchorWidth output with `% (2**24)` before the uint24 cast so that
large literal values (e.g. 1e18 from evolved constants) produce valid
Solidity instead of a compile-time overflow error.
Add test_transpiler_clamping.sh (Test 5) verifying that a Push3 program
outputting 1e18 for anchorWidth generates `uint24(... % (2**24))` and not
the raw overflowing literal. Update package.json to run both test suites.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Update tsconfig.json to use NodeNext module system (fixes CJS/ESM conflict),
enable ts-node ESM mode, and add .js extensions to relative imports so the
built output and ts-node dev script both work correctly with "type":"module".
Replace the }` heuristic in inject.sh with a brace-depth counter:
start at depth=1 after the opening {, increment on {, decrement on },
stop when depth reaches 0. This correctly handles nested if/else blocks,
loops, and structs that close at 4-space indent inside calculateParams.
Also emit a non-zero exit with a descriptive message if EOF is reached
without finding the matching closing brace.
Add test_inject_extraction.sh covering simple bodies, nested if/else,
multi-level nesting, and the EOF-without-match error case.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
dpop/bpop silently returned '0'/'false' on stack underflow instead of
throwing, so isValid() never returned false for underflowing programs.
Make dpop and bpop throw an Error on underflow so the transpiler's
existing try/catch in isValid() correctly classifies such programs as
invalid. The output-extraction phase uses state.dStack.pop() directly
(not dpop) and is unaffected.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
## What
- `tools/push3-transpiler/inject.sh` — shared transpile+inject logic used by both batch-eval and red-team-sweep
- `batch-eval.sh` — replaced inline 60-line Python block with `inject.sh` call
- `scripts/harb-evaluator/red-team-sweep.sh` — red-teams each kindergarten seed using existing `red-team.sh`, with random smoke test gate
## Why
Sweep script kept breaking because I rewrote the injection logic instead of reusing batch-eval's proven Python. Now there's one copy.
## Testing
- inject.sh tested manually on DO box with optimizer_v3 seed
- Smoke test picks random seed, injects + compiles before starting sweep
Co-authored-by: openhands <openhands@all-hands.dev>
Reviewed-on: https://codeberg.org/johba/harb/pulls/806
Reviewed-by: review_bot <review_bot@noreply.codeberg.org>
Three defensive layers so every Push3 program runs without reverting:
Layer A (transpiler/index.ts): assign bear defaults (CI=0, AS=0.3e18,
AW=100, DD=0.3e18) to all four outputs at the top of calculateParams.
Any output the evolved program does not overwrite keeps the safe default.
Layer B (transpiler/transpiler.ts): graceful stack underflow — dpop/bpop
return '0'/'false' instead of throwing, and the final output-pop falls
back to bear-default literals when fewer than 4 values remain on the
stack. Wrong output count no longer aborts transpilation.
Layer C (transpiler/transpiler.ts + index.ts): wrap the entire function
body in `unchecked {}` so integer overflow wraps (matching Push3), and
emit `(b == 0 ? 0 : a / b)` for every DYADIC./ (div-by-zero → 0,
matching Push3 no-op semantics).
Layer 2 (Optimizer.sol getLiquidityParams): clamp the three fraction
outputs (capitalInefficiency, anchorShare, discoveryDepth) to [0, 1e18]
after abi.decode so a buggy evolved program cannot produce out-of-range
values even if it runs without reverting.
Regenerated OptimizerV3Push3.sol with the updated transpiler; all 193
tests pass (34 Optimizer/OptimizerV3Push3 tests explicitly).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Reviewer noted that `< 4` only catches underflow; programs leaving 5+
values on the DYADIC stack silently passed isValid(). Change the guard
to `!== 4` so both under- and overflow are rejected, matching the
documented 'exactly 4 outputs' contract.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace silent ?? '0' fallbacks with an explicit length check that
throws when the DYADIC stack holds fewer than 4 values at program
termination. isValid() in the evolution pipeline now correctly
rejects underflow programs instead of silently scoring them as valid
with zeroed outputs.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>