minimal repro of a revert we hit on devnet doing engine -> validator -> engine callback. cant tell from the receipt whether this is expected.
the pattern is a work-engine that calls into a validator's finalize, and inside finalize the validator calls back into the engine's pay_winner to settle each winner. on the prod stack (with treasury, reputation, soul registry wired in) auto_finalize reverts. validator body never runs. trace events fire from the engine up to the line right before call(Validator, "finalize", job_id), then execution reverted with no message and effort 1037.
stripping it down to two contracts in this repo and the same revert happens. SliceEngine + SliceValidator, no treasury, no reputation, no genesis, no transfers, no fhe. just the two of them wired together. ~5500 bytes / ~926 instr combined.
repro:
1. deploy SliceEngine, no constructor args
2. deploy SliceValidator with engine address as constructor arg
3. Engine.register_validator(1, validator_addr)
4. Engine.post_job(1, 3, 100, soul_a, soul_b, soul_c) any 3 valid octra addrs
5. Engine.attest_for(soul_a, 1, "yes")
Engine.attest_for(soul_b, 1, "yes")
Engine.attest_for(soul_c, 1, "yes")
6. Engine.auto_finalize(1) reverts
receipt at step 6: success: False, effort: 627, error: execution reverted, 6 events emitted (Trace 100, 101, 102, 103, 104, 123 from the engine). trace 124 (right after call(validator, "finalize", job_id)) never fires. no validator events fire either. so the revert is inside the call but before the validator body runs. job stays at STATUS_LOCKED, Validator.is_finalized stays at 0.
we bisected this on the production version before stripping. validator with stub finalize (just emit + state-write + return true) works clean and status flips to SETTLED. validator with full body but no callback works. validator with single call(engine, "pay_winner", ...) and pay_winner with bare body (no require, no state reads) works. add require checks to pay_winner that read state-maps and it reverts. drop those guards and it passes again with the callback in place.
the failing code is inside pay_winner which the validator calls back into, but the revert lands at the engine -> validator boundary with no error message. cant tell whether the runtime rejects state-map reads in a re-entered context, the require check is genuinely failing because state isnt visible from the re-entered frame, theres an effort accounting issue at the call boundary, or something else.
is this expected under some constraint we missed. and if it is, whats the canonical pattern for "engine fans out to validator which pays per-winner via the engine". we have a pull-based workaround where Validator.finalize stores per-soul amounts in its own state and the engine has a top-level claim_winner(job_id, soul) that reads from the validator. that works clean. but want to know if push-based is supposed to work and were holding it wrong.
slice live on devnet:
- SliceEngine
oct67rX1HNhbG8gSqN5cEk2unyrvUcyWcP5AKGoq9MMjj2M - SliceValidator
oct5UQZgeYcknUo2S58Z5tjDba9Qt1YJr6ARNoqwyToHCjS
slice_repro.log is the raw deploy + run output. happy to run a different bisect variant.