fix: prevent asserter from disputing their own assertion - #117
Conversation
An asserter calling dispute() on their own pending assertion would consume the one dispute slot and guarantee both bonds back regardless of the resolver vote (since resolve() pays the winner and winner == asserter == disputer in both branches), nullifying the bond-forfeiture deterrent. Fix: - Add Error::SelfDispute = 22 to the Error enum - Add a disputer == assertion.asserter check in dispute() immediately after the NotPending and ChallengeWindowClosed guards, before any state is mutated or any bond is transferred Test: - Add test_asserter_cannot_dispute_own_assertion: verifies SelfDispute is returned, assertion remains Pending, disputer field stays None, and the asserter's token balance is unchanged after the rejected call Closes drydocs#113
collinsezedike
left a comment
There was a problem hiding this comment.
@Kayce10 thank you for the contribution. This looks good, there is nothing to flag.
Merging now.
|
@Kayce10 CI's sdk job is failing: this PR adds |
|
Hey @Kayce10, close but the sdk check does an exact diff against freshly-regenerated bindings, not just a check that the entry exists. It looks like this was hand-typed rather than actually run through the tool, the doc comment and formatting don't match what |
Error::SelfDispute was added to the contract in the previous commit but the generated SDK bindings were not updated. Add the missing entry to the Errors map in packages/tholos-sdk/src/index.ts so the sdk CI job passes.
359f95b to
157655f
Compare
collinsezedike
left a comment
There was a problem hiding this comment.
@Kayce10 thank you for the contribution. This looks good, there is nothing to flag.
Merging now.
|
@collinsezedike thanks for the opportunity to work with you on this, hope we work again. i just looked up #72 could not find it |
|
@collinsezedike |
Summary
dispute(contracts/tholos/src/lib.rs) requireddisputer.require_auth()and checked the assertion wasPendingand within the challenge window, but never checkeddisputer != assertion.asserter. An asserter could calldisputeon their own pending assertion, consuming the assertion's one dispute slot and locking out any honest third party. Becauseresolve's winner is selected purely fromassertion.asserter/assertion.disputer, whendisputer == asserterthe winner is the same address regardless of the resolver committee's actual vote, guaranteeing both bonds back and nullifying the bond-forfeiture deterrent.Changes:
Error::SelfDispute = 22to theErrorenum with a doc comment explaining the invariantdisputer == assertion.assertercheck indispute()after the auth check andNotPending/ChallengeWindowClosedguards, before any state is mutated or any bond is transferredtest_asserter_cannot_dispute_own_assertionwhich verifiesSelfDisputeis returned, the assertion remainsPending, thedisputerfield staysNone, and the asserter's token balance is unchangedTest plan
cargo fmt --check,cargo clippy --workspace --all-targets -- -D warnings, andcargo testpass locallyCONTRACT.mdupdated if the public interface changedscripts/testnet-smoke.shrun against testnet, if this changes contract behavior in a way that affects the deployed flowtest_asserter_cannot_dispute_own_assertionadded and passing; all 100 existing tests continue to passCloses #113