Skip to content

feat(soroban): decentralized dispute resolution with staked jury voting & slashing - #28

Merged
meshackyaro merged 2 commits into
workman-labs:developmentfrom
oss-dw:feat/dispute-resolution
Jul 22, 2026
Merged

feat(soroban): decentralized dispute resolution with staked jury voting & slashing#28
meshackyaro merged 2 commits into
workman-labs:developmentfrom
oss-dw:feat/dispute-resolution

Conversation

@bbjiggy

@bbjiggy bbjiggy commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Closes #18

What

Adds a new dispute-resolution Soroban contract that models dispute resolution as an on-chain state machine resolved by a staked jury using commit-reveal voting, with reward distribution to the majority and slashing of the minority and no-shows. It is the trust-minimized counterpart to escrow's single-admin resolve_dispute.

Lifecycle

open_dispute → commit_vote → reveal_vote → resolve → withdraw
   OPEN    →   COMMIT    →   REVEAL   →  RESOLVED

Two time-boxed phases are derived from the ledger clock against the dispute's two deadlines (not stored as an enum, so they can't drift out of sync):

  • Commit (now <= commit_deadline): a juror stakes juror_stake and submits commitment = sha256(salt || vote_byte || juror_xdr). Binding the juror's own address into the preimage stops a copycat from replaying someone else's commitment — the copied hash can never be revealed from a different address.
  • Reveal (commit_deadline < now <= reveal_deadline): the juror discloses (vote, salt); the contract recomputes the hash and, on a match, records the vote and bumps the running tally.
  • Resolve (now > reveal_deadline, permissionless): tallies the majority and fixes the per-winner slashed-pot share.
  • Withdraw: pull pattern (resolution never loops over an unbounded juror set), checks-effects-interactions before any transfer. Winners collect juror_stake + reward_per_winner; losers/no-shows are slashed; ties and below-quorum turnouts refund every staker with no slashing.

Tests (27, all passing)

Full commit→reveal→resolve→withdraw lifecycle for both verdicts; no-show slashing feeding the winners' pot; tie and quorum-failure refunds (including non-revealers); out-of-phase rejection for every transition; and adversarial paths — double init/commit/reveal/resolve/withdraw, wrong-vote/wrong-salt reveals, a copycat replaying another juror's commitment being unable to reveal it, and a slashed loser never draining the pot via repeated withdrawals.

Checklist

  • cargo test — 27/27 pass
  • cargo clippy -- -D warnings — clean
  • cargo fmt --check — clean (new crate)
  • Optimized wasm32v1-none release build succeeds
  • Registered in the workspace; picked up by the existing --workspace CI (Soroban CI already caches cargo deps via Swatinem/rust-cache)
  • Storage layout, authorization, errors, and security limitations documented in the workspace README

Notes / limitations (see README)

Resists free sybils (real collateral per identity) and hidden-vote manipulation (commit-reveal), but does not defend against a well-capitalized actor funding many jurors — there's no random jury selection or reputation weighting. Single-round with no appeals; honest minority jurors are slashed alongside malicious ones. Unaudited.

@meshackyaro

meshackyaro commented Jul 22, 2026

Copy link
Copy Markdown
Contributor
  • soroban-contracts/Cargo.toml
  • soroban-contracts/README.md

Please resolve conflicts in the following files:

soroban-contracts/Cargo.toml
soroban-contracts/README.md

…voting & slashing

Model dispute resolution as an on-chain state machine resolved by a staked
jury using commit-reveal voting, with reward distribution to the majority and
slashing of the minority and no-shows — a trust-minimized counterpart to
escrow's single-admin resolve_dispute.

- open_dispute (admin) starts time-boxed commit/reveal phases derived from the
  ledger clock so phases can't drift out of sync.
- commit_vote stakes collateral and stores sha256(salt || vote || juror_xdr),
  binding the commitment to the juror's address so a copycat can't replay
  someone else's commitment.
- reveal_vote checks the hash and tallies the vote; resolve tallies the
  majority and fixes the per-winner slashed-pot share.
- withdraw uses a pull pattern (no unbounded loops) with
  checks-effects-interactions; ties and below-quorum turnouts refund everyone
  with no slashing.

Includes 27 unit tests covering the full lifecycle, both verdicts, no-show
slashing, tie/quorum refunds, out-of-phase rejection, and adversarial paths
(copycat commitments, wrong reveals, repeated-withdraw draining). Documents the
storage layout, errors, model, and security limits in the workspace README and
registers the crate in the workspace.
@bbjiggy
bbjiggy force-pushed the feat/dispute-resolution branch from b7257ec to 2ff11d5 Compare July 22, 2026 16:09
@bbjiggy

bbjiggy commented Jul 22, 2026

Copy link
Copy Markdown
Contributor Author

@meshackyaro thanks for the review — conflicts are now resolved. ✅

I rebased feat/dispute-resolution onto the latest development (which now includes the governance-guard contract from #26) and resolved both files:

  • Cargo.toml — kept both new workspace members (governance-guard and dispute-resolution); Cargo.lock merged cleanly.
  • README.md — kept both contract-table rows and updated the intro to "five independent contracts". I also adjusted the governance-guard row's wording to "four of the contracts above (all except dispute-resolution)", since dispute-resolution doesn't integrate the multi-sig upgrade guard.

The PR now shows as mergeable. Re-verified locally after the rebase: 27/27 tests pass, clippy --workspace -- -D warnings clean, fmt clean, and the wasm32v1-none release build succeeds.

@meshackyaro

meshackyaro commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Closes #18

What

Adds a new dispute-resolution Soroban contract that models dispute resolution as an on-chain state machine resolved by a staked jury using commit-reveal voting, with reward distribution to the majority and slashing of the minority and no-shows. It is the trust-minimized counterpart to escrow's single-admin resolve_dispute.

Lifecycle

open_dispute → commit_vote → reveal_vote → resolve → withdraw
   OPEN    →   COMMIT    →   REVEAL   →  RESOLVED

Two time-boxed phases are derived from the ledger clock against the dispute's two deadlines (not stored as an enum, so they can't drift out of sync):

  • Commit (now <= commit_deadline): a juror stakes juror_stake and submits commitment = sha256(salt || vote_byte || juror_xdr). Binding the juror's own address into the preimage stops a copycat from replaying someone else's commitment — the copied hash can never be revealed from a different address.
  • Reveal (commit_deadline < now <= reveal_deadline): the juror discloses (vote, salt); the contract recomputes the hash and, on a match, records the vote and bumps the running tally.
  • Resolve (now > reveal_deadline, permissionless): tallies the majority and fixes the per-winner slashed-pot share.
  • Withdraw: pull pattern (resolution never loops over an unbounded juror set), checks-effects-interactions before any transfer. Winners collect juror_stake + reward_per_winner; losers/no-shows are slashed; ties and below-quorum turnouts refund every staker with no slashing.

Tests (27, all passing)

Full commit→reveal→resolve→withdraw lifecycle for both verdicts; no-show slashing feeding the winners' pot; tie and quorum-failure refunds (including non-revealers); out-of-phase rejection for every transition; and adversarial paths — double init/commit/reveal/resolve/withdraw, wrong-vote/wrong-salt reveals, a copycat replaying another juror's commitment being unable to reveal it, and a slashed loser never draining the pot via repeated withdrawals.

Checklist

  • cargo test — 27/27 pass
  • cargo clippy -- -D warnings — clean
  • cargo fmt --check — clean (new crate)
  • Optimized wasm32v1-none release build succeeds
  • Registered in the workspace; picked up by the existing --workspace CI (Soroban CI already caches cargo deps via Swatinem/rust-cache)
  • Storage layout, authorization, errors, and security limitations documented in the workspace README

Notes / limitations (see README)

Resists free sybils (real collateral per identity) and hidden-vote manipulation (commit-reveal), but does not defend against a well-capitalized actor funding many jurors — there's no random jury selection or reputation weighting. Single-round with no appeals; honest minority jurors are slashed alongside malicious ones. Unaudited.

This is a substantial addition — a full commit-reveal jury contract — and the design writeup makes it easy to follow the trust model. Nice work anticipating the obvious attacks (copycat commitment replay, unbounded-loop withdrawal, repeated-withdrawal draining) and writing tests specifically for them rather than just the happy path.

On the state machine

Deriving phase from now against the two deadlines instead of storing an enum is the right call — agreed that a stored phase field is just another thing that can desync from the clock it's supposed to track. Good judgment call to note it explicitly rather than let it look like an oversight.
Binding the juror's address into the commitment preimage is a nice detail — worth double-checking in review that every code path actually reads the caller's own address for that check (not a passed-in parameter that could be spoofed), since that binding is doing a lot of the security work here.

On resolve/withdraw

Permissionless resolve plus pull-pattern withdraw with checks-effects-interactions is the right pattern for a jury of unbounded size — glad this wasn't done with a loop over all jurors sending payouts directly.
Tie and below-quorum refunds with no slashing make sense as a conservative default (don't punish anyone when the process itself didn't produce a clear signal). Two questions:
What's the quorum threshold, and is it fixed at contract level or configurable per-dispute at open_dispute? If fixed, is there a risk of it being wrong for very high or very low stake disputes?
For a tie specifically — is majority strictly >50% of revealed votes, or could a 2-way split with a small juror count trigger "tie" more often than intended? Would like to see the exact tie condition, even if it's just pointing me to the line.

On the stated limitations

Appreciate that the README is upfront about what this doesn't defend against — a well-capitalized actor funding many jurors, and honest-minority-gets-slashed-too. These are real, non-cosmetic limitations for a dispute system handling real stakes, not nitpicks:
Is there a plan (even just a tracked issue) for random jury selection or reputation weighting, or is that explicitly out of scope for dispute-resolution v1 the way signer rotation was out of scope in the governance-guard PR? If it's a known v2, worth linking a tracking issue now so it doesn't read as abandoned.
"Honest minority jurors are slashed alongside malicious ones" is a real economic disincentive to participate honestly in a close call — is that considered acceptable given the commit-reveal protection against manipulation, or is it a known rough edge? Just want the reasoning on record here since it affects juror incentive design, not just this contract's correctness.
Single-round, no appeals, unaudited — all fine to ship as v1 as long as these are loud and visible wherever this contract gets deployed/documented for integrators, not just in this PR description.

On tests and CI

27 tests covering both verdicts, no-show slashing, tie/quorum refunds (including non-revealers, which is easy to miss), out-of-phase rejection for every transition, and the adversarial paths (double-everything, wrong vote/salt, copycat replay, repeated-withdrawal draining) is a strong set — this reads like someone thought about how they'd attack their own contract, which is what I want to see on something holding stake.
Good that it's registered in the workspace and picked up by existing CI rather than needing a new pipeline.

Nothing here blocks approval in principle — the core mechanism looks sound and well-tested. I'd like answers on the quorum/tie mechanics and a decision (fix now vs. tracked issue) on the sybil-resistance and honest-minority-slashing limitations before merging, given this is handling real staked funds once deployed.

@bbjiggy

bbjiggy commented Jul 22, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the thorough review — the questions land exactly where the security-relevant decisions are. Answers below with line references, and a fix-now-vs-tracked call on the two limitations.

Address binding (your double-check)

Confirmed: every path uses the caller's own authenticated address, never a separately-spoofable parameter.

  • commit_vote calls juror.require_auth() (lib.rs:268) and stores the record under DataKey::Juror(dispute_id, juror) (:278). The contract never computes the hash at commit time — the commitment is opaque.
  • reveal_vote calls juror.require_auth() (:320), loads the record keyed by that same juror (:331), and recomputes commitment_hash(&env, &juror, vote, &salt) (:341) using that authenticated address.

So to reveal record R, you must (a) authenticate as juror J, and (b) have stored_commitment(J) == sha256(salt || vote || J_xdr). A copycat J' who committed sha256(… || J_xdr) has it stored under (id, J') and reveal recomputes with J'_xdr — it can never match. The address doing the binding is structurally the same one require_auth gates. No path reads an address that isn't the authenticated caller.

Quorum threshold

It's config.min_jurorscontract-level and immutable, set once in initialize (part of Config, :83–85); there is no per-dispute override in open_dispute. The check is on revealed votes, not commits: at resolve, revealed = yes_count + no_count, and revealed < min_jurorsQuorumFailed (:381–384).

Your instinct is right that one fixed (juror_stake, min_jurors) pair per instance is a real constraint — juror_stake is contract-level too. The intended deployment model for v1 is one instance per stake tier (deploy separate contracts for low- vs high-value disputes), and the README calls this out under the sybil/stake-weighting limitation ("Set juror_stake/min_jurors relative to the value at stake"). Per-dispute config is a clean v2 change (it only touches open_dispute + the Dispute struct) — happy to track it if you'd prefer that over per-instance tiering.

Tie condition

Exact lines are resolve, :385–390:

} else if dispute.yes_count > dispute.no_count {
    (Outcome::Plaintiff, dispute.yes_count)
} else if dispute.no_count > dispute.yes_count {
    (Outcome::Defendant, dispute.no_count)
} else {
    (Outcome::Tie, 0u32)
};

Because there are only two sides, "more revealed votes than the other side" is strictly >50% of revealed votes — the winner condition is a strict majority, not a plurality with a third bucket. Tie fires only on exact equality of yes_count and no_count, which is only reachable with an even number of revealed votes. So small juror counts don't make ties more likely than the arithmetic dictates: 2–1 decides, only 2–2/3–3 tie. No-shows never count toward either side (only revealed votes are tallied) but they do count in the slashing denominator juror_count (:398), which is the "paid to show up" incentive.

The two limitations — my recommendation: track for v2, don't fix here

Both are already documented loudly in the README's Security considerations / known limitations section (the "Jury sybil / stake-weighting" and "No appeals and majority-takes-all slashing" bullets), so they're integrator-visible where the contract is documented, not just buried in this PR description.

Sybil-resistance / weighted or random jury selection — recommending tracked v2, explicitly out of scope for v1, mirroring how signer rotation was deferred in the governance-guard PR. v1 is deliberately one-stake-one-vote with an open permissionless jury; it resists free sybils (real collateral per identity) but not a well-capitalized actor. Worth noting there's already a concrete building block for the weighting version: the sybil-resistant weighted reputation scoring from signed attestations added in #20 is the natural score/selection input for a reputation-weighted jury. If you agree, I'll open a tracking issue framing v2 as "reputation-weighted / randomized jury selection, built on #20" and link it from the README bullet so it doesn't read as abandoned.

Honest-minority-slashing — this is an intentional design choice, and I'd like the reasoning on record rather than treating it as a bug: it's a Schelling-point coordination game (Kleros-style). Commit-reveal is precisely what makes slashing the minority defensible — jurors can't observe and copy the leader, so the only way to reliably land in the majority is to independently predict the honest/focal outcome. Remove minority-slashing and you remove the incentive to vote carefully at all. The accepted cost is that in a genuinely close or ambiguous call, an honest juror on the losing side is slashed alongside malicious ones. For v1 that's a known rough edge, not a correctness bug; the softening levers (margin-based partial refunds inside a small spread, or an appeal round) are exactly the kind of thing an appeals/v2 design should own. I'll fold this into the same v2 tracking issue if you'd like.

Single-round / no-appeals / unaudited are all v1-visible in the same README section. If you want them louder still (e.g., a one-line "⚠️ v1, unaudited, no appeals" banner at the top of the dispute-resolution interface section for integrators who skim), say the word and I'll add it in this PR.

Net: nothing here changes the core mechanism — I'd like to (1) open one v2 tracking issue covering sybil-resistant/weighted selection (on #20) and the honest-minority softening, link it from the README, and (2) optionally add a per-dispute-config note. Let me know if you'd rather I pull per-dispute quorum into this PR instead of deferring it.

@meshackyaro

Copy link
Copy Markdown
Contributor

Thanks for the thorough review — the questions land exactly where the security-relevant decisions are. Answers below with line references, and a fix-now-vs-tracked call on the two limitations.

Address binding (your double-check)

Confirmed: every path uses the caller's own authenticated address, never a separately-spoofable parameter.

  • commit_vote calls juror.require_auth() (lib.rs:268) and stores the record under DataKey::Juror(dispute_id, juror) (:278). The contract never computes the hash at commit time — the commitment is opaque.
  • reveal_vote calls juror.require_auth() (:320), loads the record keyed by that same juror (:331), and recomputes commitment_hash(&env, &juror, vote, &salt) (:341) using that authenticated address.

So to reveal record R, you must (a) authenticate as juror J, and (b) have stored_commitment(J) == sha256(salt || vote || J_xdr). A copycat J' who committed sha256(… || J_xdr) has it stored under (id, J') and reveal recomputes with J'_xdr — it can never match. The address doing the binding is structurally the same one require_auth gates. No path reads an address that isn't the authenticated caller.

Quorum threshold

It's config.min_jurorscontract-level and immutable, set once in initialize (part of Config, :83–85); there is no per-dispute override in open_dispute. The check is on revealed votes, not commits: at resolve, revealed = yes_count + no_count, and revealed < min_jurorsQuorumFailed (:381–384).

Your instinct is right that one fixed (juror_stake, min_jurors) pair per instance is a real constraint — juror_stake is contract-level too. The intended deployment model for v1 is one instance per stake tier (deploy separate contracts for low- vs high-value disputes), and the README calls this out under the sybil/stake-weighting limitation ("Set juror_stake/min_jurors relative to the value at stake"). Per-dispute config is a clean v2 change (it only touches open_dispute + the Dispute struct) — happy to track it if you'd prefer that over per-instance tiering.

Tie condition

Exact lines are resolve, :385–390:

} else if dispute.yes_count > dispute.no_count {
    (Outcome::Plaintiff, dispute.yes_count)
} else if dispute.no_count > dispute.yes_count {
    (Outcome::Defendant, dispute.no_count)
} else {
    (Outcome::Tie, 0u32)
};

Because there are only two sides, "more revealed votes than the other side" is strictly >50% of revealed votes — the winner condition is a strict majority, not a plurality with a third bucket. Tie fires only on exact equality of yes_count and no_count, which is only reachable with an even number of revealed votes. So small juror counts don't make ties more likely than the arithmetic dictates: 2–1 decides, only 2–2/3–3 tie. No-shows never count toward either side (only revealed votes are tallied) but they do count in the slashing denominator juror_count (:398), which is the "paid to show up" incentive.

The two limitations — my recommendation: track for v2, don't fix here

Both are already documented loudly in the README's Security considerations / known limitations section (the "Jury sybil / stake-weighting" and "No appeals and majority-takes-all slashing" bullets), so they're integrator-visible where the contract is documented, not just buried in this PR description.

Sybil-resistance / weighted or random jury selection — recommending tracked v2, explicitly out of scope for v1, mirroring how signer rotation was deferred in the governance-guard PR. v1 is deliberately one-stake-one-vote with an open permissionless jury; it resists free sybils (real collateral per identity) but not a well-capitalized actor. Worth noting there's already a concrete building block for the weighting version: the sybil-resistant weighted reputation scoring from signed attestations added in #20 is the natural score/selection input for a reputation-weighted jury. If you agree, I'll open a tracking issue framing v2 as "reputation-weighted / randomized jury selection, built on #20" and link it from the README bullet so it doesn't read as abandoned.

Honest-minority-slashing — this is an intentional design choice, and I'd like the reasoning on record rather than treating it as a bug: it's a Schelling-point coordination game (Kleros-style). Commit-reveal is precisely what makes slashing the minority defensible — jurors can't observe and copy the leader, so the only way to reliably land in the majority is to independently predict the honest/focal outcome. Remove minority-slashing and you remove the incentive to vote carefully at all. The accepted cost is that in a genuinely close or ambiguous call, an honest juror on the losing side is slashed alongside malicious ones. For v1 that's a known rough edge, not a correctness bug; the softening levers (margin-based partial refunds inside a small spread, or an appeal round) are exactly the kind of thing an appeals/v2 design should own. I'll fold this into the same v2 tracking issue if you'd like.

Single-round / no-appeals / unaudited are all v1-visible in the same README section. If you want them louder still (e.g., a one-line "⚠️ v1, unaudited, no appeals" banner at the top of the dispute-resolution interface section for integrators who skim), say the word and I'll add it in this PR.

Net: nothing here changes the core mechanism — I'd like to (1) open one v2 tracking issue covering sybil-resistant/weighted selection (on #20) and the honest-minority softening, link it from the README, and (2) optionally add a per-dispute-config note. Let me know if you'd rather I pull per-dispute quorum into this PR instead of deferring it.

Address binding, quorum mechanics, and the tie condition are all resolved for me — thanks for the line-level walkthrough, that's exactly what I needed to sign off on those without re-deriving it myself.

On the two open items:

Warning banner — yes, please add it in this PR. One-line, top of the dispute-resolution interface section, something like "⚠️ v1, unaudited, no appeals — see Security considerations." Trivial to add, real value for anyone integrating without reading the whole README top to bottom.

V2 tracking issue — please open it now and link it from both README limitation bullets (sybil/weighted-selection and honest-minority-slashing) as part of this PR's diff, not as a follow-up promise. Same bar we held the governance-guard PR to with #27. Go ahead and frame it the way you described — reputation-weighted/randomized selection built on #20, plus the minority-slashing softening levers (margin-based partial refunds, appeal round) as candidate v2 directions, not commitments.

Per-dispute config — this is the one I want to slow down on. You've correctly identified that juror_stake and min_jurors being contract-level and immutable means the real deployment model is one instance per stake tier. Before I file that under "clean v2 change, defer it," I want to know: what's the actual launch plan? If we're expecting disputes across a wide range of stake sizes from day one, standing up and maintaining N contract instances is real operational overhead, and I'd rather we decide that's acceptable on purpose than back into it because deferring config felt like the natural default. If early usage is going to be narrow (one or two stake bands), current scope is fine and I'm happy to defer. Can you give me a sense of what the initial deployment target looks like? That answer decides whether per-dispute config comes into this PR or stays in the v2 issue.

… issue

Add an integrator-facing v1/unaudited/no-appeals banner at the top of the
dispute-resolution interface section, and link the v2 tracking issue (workman-labs#29)
from both the jury-sybil and majority-takes-all-slashing limitation bullets so
the deferred sybil-resistant/weighted selection and honest-minority softening
read as scoped follow-ups rather than gaps.
@bbjiggy

bbjiggy commented Jul 22, 2026

Copy link
Copy Markdown
Contributor Author

Both README items are done and in this PR's diff now (commit 45e7f70), not as follow-up promises:

Per-dispute config — straight answer: the launch target isn't pinned yet

I don't want to file this under "narrow, so defer" and have you sign off on a scope decision that's really just me guessing. The honest state is the initial stake-band breadth isn't decided, so I can't tell you today whether day-one usage is one or two bands or a wide range — which is exactly the input your question turns on.

Given that, I'd rather not back into either outcome. My recommendation:

So the decision I'm actually asking you to make: is it acceptable to merge v1 on the current per-instance-per-stake-tier model with the config question tracked-and-open, on the understanding that a wide-stake-range launch requires making this call (per-dispute config vs. deliberately running N instances) before that launch, not before this merge? If yes, we're unblocked. If you'd rather not merge until the launch scope is pinned, I can pull that decision forward — but that's a product/launch-planning call on our side, not a contract-correctness one, so I didn't want to hold the mechanism hostage to it without checking.

If you'd prefer it tracked as its own issue rather than a sub-note on #29, say so and I'll split it out.

@meshackyaro

Copy link
Copy Markdown
Contributor

Both README items are done and in this PR's diff now (commit 45e7f70), not as follow-up promises:

Per-dispute config — straight answer: the launch target isn't pinned yet

I don't want to file this under "narrow, so defer" and have you sign off on a scope decision that's really just me guessing. The honest state is the initial stake-band breadth isn't decided, so I can't tell you today whether day-one usage is one or two bands or a wide range — which is exactly the input your question turns on.

Given that, I'd rather not back into either outcome. My recommendation:

So the decision I'm actually asking you to make: is it acceptable to merge v1 on the current per-instance-per-stake-tier model with the config question tracked-and-open, on the understanding that a wide-stake-range launch requires making this call (per-dispute config vs. deliberately running N instances) before that launch, not before this merge? If yes, we're unblocked. If you'd rather not merge until the launch scope is pinned, I can pull that decision forward — but that's a product/launch-planning call on our side, not a contract-correctness one, so I didn't want to hold the mechanism hostage to it without checking.

If you'd prefer it tracked as its own issue rather than a sub-note on #29, say so and I'll split it out.

Both README items look good — the banner is direct and the tracking issue is well-scoped and properly cross-linked. Appreciate you not letting either wait on the config question.

On per-dispute config: you're right to push back rather than let me sign off on a scope decision neither of us can actually make yet. Your reasoning holds — the mechanism itself is correct and complete regardless of the config question, the change stays cheap whenever we do land it, and tying the merge of a working, well-tested contract to an unrelated launch-planning decision would be the wrong kind of caution.

So: yes, merging v1 on the current per-instance-per-stake-tier model is acceptable, with the explicit understanding that before any wide-stake-range launch, someone has to make the per-dispute-config-vs-N-instances call — and that gate lives at the launch-planning stage, not here. I'll take that as an action item on our side; it shouldn't block you.

On tracking: keep it as the sub-note on #29 rather than splitting it out. It's genuinely related context for whoever picks up the v2 work, and splitting it into its own issue right now would just create a second thing to keep in sync with a decision that isn't ready to be made yet. If it turns out to need its own timeline once the launch target is pinned, we can split it then.

This is good to merge from my side. Thanks for the thorough back-and-forth on this one — the README and issue trail you've left make this easy for the next person to pick up context on, which matters as much as the contract code for something handling real stakes.

GREAT JOB AND WELL DONE!

@meshackyaro meshackyaro 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.

One more thing before I merge.

This whole thread is a good example of how to work through review on security-sensitive code: precise answers, honest "I don't know yet" instead of guessing and documentation left in a state the next person can actually use.

Appreciated the rigor across all of this. Merging now, and thanks for your honest contributions

@meshackyaro
meshackyaro merged commit 28b3c4e into workman-labs:development Jul 22, 2026
1 check passed
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.

Decentralized Dispute Resolution with Staked Jury Voting & Slashing

2 participants