Summary
resolve (contracts/tholos/src/lib.rs:757-833) checks that the caller is a member of the assertion's snapshotted resolver committee (assertion.resolvers.contains(&resolver)) and hasn't already voted, but never checks whether resolver is also the assertion's asserter or disputer. Nothing in initialize, update_resolvers, propose_rotation, or dispute prevents a resolver committee member from later becoming an asserter or disputer on their own dispute either.
Impact: a resolver who is also a party to the dispute can vote in their own favor. In the degenerate but valid case of a size-1 committee (the minimum odd committee size initialize allows), a single address acting as both disputer and sole resolver can guarantee their own win with zero collusion needed from anyone else. Even in a larger committee, one self-interested vote is a real thumb on the scale in a system whose entire premise is neutral dispute resolution.
Scope
- Reject a
resolve call where resolver == assertion.asserter || resolver == assertion.disputer.
- Consider whether a minimum committee size larger than 1 should be enforced at
initialize/update_resolvers, independent of this check, since a 1-member committee makes any conflict-of-interest check moot by construction (majority of 1 is trivially reached by that one member alone).
- Add tests covering: a resolver who is the asserter is rejected, a resolver who is the disputer is rejected, and (if a minimum committee size is added) that
initialize/update_resolvers reject a too-small committee.
Proposed approach
Add the identity check as an early guard in resolve, alongside the existing NotAResolver/AlreadyVoted checks, returning a new Error variant (e.g. ConflictOfInterest). The minimum-committee-size question is a separate, smaller design decision the assignee should raise if they think it's in scope here versus a follow-up issue.
Summary
resolve(contracts/tholos/src/lib.rs:757-833) checks that the caller is a member of the assertion's snapshotted resolver committee (assertion.resolvers.contains(&resolver)) and hasn't already voted, but never checks whetherresolveris also the assertion'sasserterordisputer. Nothing ininitialize,update_resolvers,propose_rotation, ordisputeprevents a resolver committee member from later becoming an asserter or disputer on their own dispute either.Impact: a resolver who is also a party to the dispute can vote in their own favor. In the degenerate but valid case of a size-1 committee (the minimum odd committee size
initializeallows), a single address acting as both disputer and sole resolver can guarantee their own win with zero collusion needed from anyone else. Even in a larger committee, one self-interested vote is a real thumb on the scale in a system whose entire premise is neutral dispute resolution.Scope
resolvecall whereresolver == assertion.asserter || resolver == assertion.disputer.initialize/update_resolvers, independent of this check, since a 1-member committee makes any conflict-of-interest check moot by construction (majority of 1 is trivially reached by that one member alone).initialize/update_resolversreject a too-small committee.Proposed approach
Add the identity check as an early guard in
resolve, alongside the existingNotAResolver/AlreadyVotedchecks, returning a newErrorvariant (e.g.ConflictOfInterest). The minimum-committee-size question is a separate, smaller design decision the assignee should raise if they think it's in scope here versus a follow-up issue.