contracts/src/arbitration/dispute-kits/DisputeKitSybilResistant.sol
// SPDX-License-Identifier: MIT
/// @custom:authors: [@unknownunknown1, @jaybuidl]
/// @custom:reviewers: []
/// @custom:auditors: []
/// @custom:bounties: []
/// @custom:deployments: []
pragma solidity 0.8.24;
import "../KlerosCore.sol";
import "../interfaces/IDisputeKit.sol";
import "../../proxy/UUPSProxiable.sol";
import "../../proxy/Initializable.sol";
interface IProofOfHumanity {
/// @dev Return true if the submission is registered and not expired.
/// @param _submissionID The address of the submission.
/// @return Whether the submission is registered or not.
function isRegistered(address _submissionID) external view returns (bool);
}
/// @title DisputeKitSybilResistant
/// Dispute kit implementation adapted from DisputeKitClassic
/// - a drawing system: at most 1 vote per juror registered on Proof of Humanity,
/// - a vote aggregation system: plurality,
/// - an incentive system: equal split between coherent votes,
/// - an appeal system: fund 2 choices only, vote on any choice.
contract DisputeKitSybilResistant is IDisputeKit, Initializable, UUPSProxiable {
// ************************************* //
// * Structs * //
// ************************************* //
struct Dispute {
Round[] rounds; // Rounds of the dispute. 0 is the default round, and [1, ..n] are the appeal rounds.
uint256 numberOfChoices; // The number of choices jurors have when voting. This does not include choice `0` which is reserved for "refuse to arbitrate".
bool jumped; // True if dispute jumped to a parent dispute kit and won't be handled by this DK anymore.
mapping(uint256 => uint256) coreRoundIDToLocal; // Maps id of the round in the core contract to the index of the round of related local dispute.
bytes extraData; // Extradata for the dispute.
}
struct Round {
Vote[] votes; // Former votes[_appeal][].
uint256 winningChoice; // The choice with the most votes. Note that in the case of a tie, it is the choice that reached the tied number of votes first.
mapping(uint256 => uint256) counts; // The sum of votes for each choice in the form `counts[choice]`.
bool tied; // True if there is a tie, false otherwise.
uint256 totalVoted; // Former uint[_appeal] votesInEachRound.
uint256 totalCommitted; // Former commitsInRound.
mapping(uint256 choiceId => uint256) paidFees; // Tracks the fees paid for each choice in this round.
mapping(uint256 choiceId => bool) hasPaid; // True if this choice was fully funded, false otherwise.
mapping(address account => mapping(uint256 choiceId => uint256)) contributions; // Maps contributors to their contributions for each choice.
uint256 feeRewards; // Sum of reimbursable appeal fees available to the parties that made contributions to the ruling that ultimately wins a dispute.
uint256[] fundedChoices; // Stores the choices that are fully funded.
uint256 nbVotes; // Maximal number of votes this dispute can get.
mapping(address drawnAddress => bool) alreadyDrawn; // Set to 'true' if the address has already been drawn, so it can't be drawn more than once.
}
struct Vote {
address account; // The address of the juror.
bytes32 commit; // The commit of the juror. For courts with hidden votes.
uint256 choice; // The choice of the juror.
bool voted; // True if the vote has been cast.
}
// ************************************* //
// * Storage * //
// ************************************* //
uint256 public constant WINNER_STAKE_MULTIPLIER = 10000; // Multiplier of the appeal cost that the winner has to pay as fee stake for a round in basis points. Default is 1x of appeal fee.
uint256 public constant LOSER_STAKE_MULTIPLIER = 20000; // Multiplier of the appeal cost that the loser has to pay as fee stake for a round in basis points. Default is 2x of appeal fee.
uint256 public constant LOSER_APPEAL_PERIOD_MULTIPLIER = 5000; // Multiplier of the appeal period for the choice that wasn't voted for in the previous round, in basis points. Default is 1/2 of original appeal period.
uint256 public constant ONE_BASIS_POINT = 10000; // One basis point, for scaling.
address public governor; // The governor of the contract.
KlerosCore public core; // The Kleros Core arbitrator
Dispute[] public disputes; // Array of the locally created disputes.
mapping(uint256 => uint256) public coreDisputeIDToLocal; // Maps the dispute ID in Kleros Core to the local dispute ID.
IProofOfHumanity public poh; // The Proof of Humanity registry
// ************************************* //
// * Events * //
// ************************************* //
/// @dev To be emitted when a dispute is created.
/// @param _coreDisputeID The identifier of the dispute in the Arbitrator contract.
/// @param _numberOfChoices The number of choices available in the dispute.
/// @param _extraData The extra data for the dispute.
event DisputeCreation(uint256 indexed _coreDisputeID, uint256 _numberOfChoices, bytes _extraData);
/// @dev To be emitted when a vote commitment is cast.
/// @param _coreDisputeID The identifier of the dispute in the Arbitrator contract.
/// @param _juror The address of the juror casting the vote commitment.
/// @param _voteIDs The identifiers of the votes in the dispute.
/// @param _commit The commitment of the juror.
event CommitCast(uint256 indexed _coreDisputeID, address indexed _juror, uint256[] _voteIDs, bytes32 _commit);
/// @dev To be emitted when a funding contribution is made.
/// @param _coreDisputeID The identifier of the dispute in the Arbitrator contract.
/// @param _coreRoundID The identifier of the round in the Arbitrator contract.
/// @param _choice The choice that is being funded.
/// @param _contributor The address of the contributor.
/// @param _amount The amount contributed.
event Contribution(
uint256 indexed _coreDisputeID,
uint256 indexed _coreRoundID,
uint256 _choice,
address indexed _contributor,
uint256 _amount
);
/// @dev To be emitted when the contributed funds are withdrawn.
/// @param _coreDisputeID The identifier of the dispute in the Arbitrator contract.
/// @param _coreRoundID The identifier of the round in the Arbitrator contract.
/// @param _choice The choice that is being funded.
/// @param _contributor The address of the contributor.
/// @param _amount The amount withdrawn.
event Withdrawal(
uint256 indexed _coreDisputeID,
uint256 indexed _coreRoundID,
uint256 _choice,
address indexed _contributor,
uint256 _amount
);
/// @dev To be emitted when a choice is fully funded for an appeal.
/// @param _coreDisputeID The identifier of the dispute in the Arbitrator contract.
/// @param _coreRoundID The identifier of the round in the Arbitrator contract.
/// @param _choice The choice that is being funded.
event ChoiceFunded(uint256 indexed _coreDisputeID, uint256 indexed _coreRoundID, uint256 indexed _choice);
// ************************************* //
// * Modifiers * //
// ************************************* //
modifier onlyByGovernor() {
require(governor == msg.sender, "Access not allowed: Governor only.");
_;
}
modifier onlyByCore() {
require(address(core) == msg.sender, "Access not allowed: KlerosCore only.");
_;
}
modifier notJumped(uint256 _coreDisputeID) {
require(!disputes[coreDisputeIDToLocal[_coreDisputeID]].jumped, "Dispute jumped to a parent DK!");
_;
}
// ************************************* //
// * Constructor * //
// ************************************* //
/// @dev Constructor, initializing the implementation to reduce attack surface.
constructor() {
_disableInitializers();
}
/// @dev Initializer.
/// @param _governor The governor's address.
/// @param _core The KlerosCore arbitrator.
/// @param _poh The Proof of Humanity registry.
function initialize(address _governor, KlerosCore _core, IProofOfHumanity _poh) external reinitializer(1) {
governor = _governor;
core = _core;
poh = _poh;
}
// ************************ //
// * Governance * //
// ************************ //
/// @dev Access Control to perform implementation upgrades (UUPS Proxiable)
/// Only the governor can perform upgrades (`onlyByGovernor`)
function _authorizeUpgrade(address) internal view override onlyByGovernor {
// NOP
}
/// @dev Allows the governor to call anything on behalf of the contract.
/// @param _destination The destination of the call.
/// @param _amount The value sent with the call.
/// @param _data The data sent with the call.
function executeGovernorProposal(
address _destination,
uint256 _amount,
bytes memory _data
) external onlyByGovernor {
(bool success, ) = _destination.call{value: _amount}(_data);
require(success, "Unsuccessful call");
}
/// @dev Changes the `governor` storage variable.
/// @param _governor The new value for the `governor` storage variable.
function changeGovernor(address payable _governor) external onlyByGovernor {
governor = _governor;
}
/// @dev Changes the `core` storage variable.
/// @param _core The new value for the `core` storage variable.
function changeCore(address _core) external onlyByGovernor {
core = KlerosCore(_core);
}
/// @dev Changes the `poh` storage variable.
/// @param _poh The new value for the `poh` storage variable.
function changePoh(address _poh) external onlyByGovernor {
poh = IProofOfHumanity(_poh);
}
// ************************************* //
// * State Modifiers * //
// ************************************* //
/// @dev Creates a local dispute and maps it to the dispute ID in the Core contract.
/// Note: Access restricted to Kleros Core only.
/// @param _coreDisputeID The ID of the dispute in Kleros Core.
/// @param _numberOfChoices Number of choices of the dispute
/// @param _extraData Additional info about the dispute, for possible use in future dispute kits.
/// @param _nbVotes Number of votes for this dispute.
function createDispute(
uint256 _coreDisputeID,
uint256 _numberOfChoices,
bytes calldata _extraData,
uint256 _nbVotes
) external override onlyByCore {
uint256 localDisputeID = disputes.length;
Dispute storage dispute = disputes.push();
dispute.numberOfChoices = _numberOfChoices;
dispute.extraData = _extraData;
// New round in the Core should be created before the dispute creation in DK.
dispute.coreRoundIDToLocal[core.getNumberOfRounds(_coreDisputeID) - 1] = dispute.rounds.length;
Round storage round = dispute.rounds.push();
round.nbVotes = _nbVotes;
round.tied = true;
coreDisputeIDToLocal[_coreDisputeID] = localDisputeID;
emit DisputeCreation(_coreDisputeID, _numberOfChoices, _extraData);
}
/// @dev Draws the juror from the sortition tree. The drawn address is picked up by Kleros Core.
/// Note: Access restricted to Kleros Core only.
/// @param _coreDisputeID The ID of the dispute in Kleros Core.
/// @param _nonce Nonce of the drawing iteration.
/// @return drawnAddress The drawn address.
function draw(
uint256 _coreDisputeID,
uint256 _nonce
) external override onlyByCore notJumped(_coreDisputeID) returns (address drawnAddress) {
Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]];
Round storage round = dispute.rounds[dispute.rounds.length - 1];
ISortitionModule sortitionModule = core.sortitionModule();
(uint96 courtID, , , , ) = core.disputes(_coreDisputeID);
bytes32 key = bytes32(uint256(courtID)); // Get the ID of the tree.
// TODO: Handle the situation when no one has staked yet.
drawnAddress = sortitionModule.draw(key, _coreDisputeID, _nonce);
if (_postDrawCheck(_coreDisputeID, drawnAddress) && !round.alreadyDrawn[drawnAddress]) {
round.votes.push(Vote({account: drawnAddress, commit: bytes32(0), choice: 0, voted: false}));
round.alreadyDrawn[drawnAddress] = true;
} else {
drawnAddress = address(0);
}
}
/// @dev Sets the caller's commit for the specified votes. It can be called multiple times during the
/// commit period, each call overrides the commits of the previous one.
/// `O(n)` where
/// `n` is the number of votes.
/// @param _coreDisputeID The ID of the dispute in Kleros Core.
/// @param _voteIDs The IDs of the votes.
/// @param _commit The commit. Note that justification string is a part of the commit.
function castCommit(
uint256 _coreDisputeID,
uint256[] calldata _voteIDs,
bytes32 _commit
) external notJumped(_coreDisputeID) {
(, , KlerosCore.Period period, , ) = core.disputes(_coreDisputeID);
require(period == KlerosCoreBase.Period.commit, "The dispute should be in Commit period.");
require(_commit != bytes32(0), "Empty commit.");
Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]];
Round storage round = dispute.rounds[dispute.rounds.length - 1];
for (uint256 i = 0; i < _voteIDs.length; i++) {
require(round.votes[_voteIDs[i]].account == msg.sender, "The caller has to own the vote.");
round.votes[_voteIDs[i]].commit = _commit;
}
round.totalCommitted += _voteIDs.length;
emit CommitCast(_coreDisputeID, msg.sender, _voteIDs, _commit);
}
/// @dev Sets the caller's choices for the specified votes.
/// `O(n)` where
/// `n` is the number of votes.
/// @param _coreDisputeID The ID of the dispute in Kleros Core.
/// @param _voteIDs The IDs of the votes.
/// @param _choice The choice.
/// @param _salt The salt for the commit if the votes were hidden.
/// @param _justification Justification of the choice.
function castVote(
uint256 _coreDisputeID,
uint256[] calldata _voteIDs,
uint256 _choice,
uint256 _salt,
string memory _justification
) external notJumped(_coreDisputeID) {
(, , KlerosCore.Period period, , ) = core.disputes(_coreDisputeID);
require(period == KlerosCoreBase.Period.vote, "The dispute should be in Vote period.");
require(_voteIDs.length > 0, "No voteID provided");
Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]];
require(_choice <= dispute.numberOfChoices, "Choice out of bounds");
Round storage round = dispute.rounds[dispute.rounds.length - 1];
(uint96 courtID, , , , ) = core.disputes(_coreDisputeID);
(, bool hiddenVotes, , , , , ) = core.courts(courtID);
// Save the votes.
for (uint256 i = 0; i < _voteIDs.length; i++) {
require(round.votes[_voteIDs[i]].account == msg.sender, "The caller has to own the vote.");
require(
!hiddenVotes || round.votes[_voteIDs[i]].commit == keccak256(abi.encodePacked(_choice, _salt)),
"The commit must match the choice in courts with hidden votes."
);
require(!round.votes[_voteIDs[i]].voted, "Vote already cast.");
round.votes[_voteIDs[i]].choice = _choice;
round.votes[_voteIDs[i]].voted = true;
}
round.totalVoted += _voteIDs.length;
round.counts[_choice] += _voteIDs.length;
if (_choice == round.winningChoice) {
if (round.tied) round.tied = false;
} else {
// Voted for another choice.
if (round.counts[_choice] == round.counts[round.winningChoice]) {
// Tie.
if (!round.tied) round.tied = true;
} else if (round.counts[_choice] > round.counts[round.winningChoice]) {
// New winner.
round.winningChoice = _choice;
round.tied = false;
}
}
emit VoteCast(_coreDisputeID, msg.sender, _voteIDs, _choice, _justification);
}
/// @dev Manages contributions, and appeals a dispute if at least two choices are fully funded.
/// Note that the surplus deposit will be reimbursed.
/// @param _coreDisputeID Index of the dispute in Kleros Core.
/// @param _choice A choice that receives funding.
function fundAppeal(uint256 _coreDisputeID, uint256 _choice) external payable notJumped(_coreDisputeID) {
Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]];
require(_choice <= dispute.numberOfChoices, "There is no such ruling to fund.");
(uint256 appealPeriodStart, uint256 appealPeriodEnd) = core.appealPeriod(_coreDisputeID);
require(block.timestamp >= appealPeriodStart && block.timestamp < appealPeriodEnd, "Appeal period is over.");
uint256 multiplier;
(uint256 ruling, , ) = this.currentRuling(_coreDisputeID);
if (ruling == _choice) {
multiplier = WINNER_STAKE_MULTIPLIER;
} else {
require(
block.timestamp - appealPeriodStart <
((appealPeriodEnd - appealPeriodStart) * LOSER_APPEAL_PERIOD_MULTIPLIER) / ONE_BASIS_POINT,
"Appeal period is over for loser"
);
multiplier = LOSER_STAKE_MULTIPLIER;
}
Round storage round = dispute.rounds[dispute.rounds.length - 1];
uint256 coreRoundID = core.getNumberOfRounds(_coreDisputeID) - 1;
require(!round.hasPaid[_choice], "Appeal fee is already paid.");
uint256 appealCost = core.appealCost(_coreDisputeID);
uint256 totalCost = appealCost + (appealCost * multiplier) / ONE_BASIS_POINT;
// Take up to the amount necessary to fund the current round at the current costs.
uint256 contribution;
if (totalCost > round.paidFees[_choice]) {
contribution = totalCost - round.paidFees[_choice] > msg.value // Overflows and underflows will be managed on the compiler level.
? msg.value
: totalCost - round.paidFees[_choice];
emit Contribution(_coreDisputeID, coreRoundID, _choice, msg.sender, contribution);
}
round.contributions[msg.sender][_choice] += contribution;
round.paidFees[_choice] += contribution;
if (round.paidFees[_choice] >= totalCost) {
round.feeRewards += round.paidFees[_choice];
round.fundedChoices.push(_choice);
round.hasPaid[_choice] = true;
emit ChoiceFunded(_coreDisputeID, coreRoundID, _choice);
}
if (round.fundedChoices.length > 1) {
// At least two sides are fully funded.
round.feeRewards = round.feeRewards - appealCost;
if (core.isDisputeKitJumping(_coreDisputeID)) {
// Don't create a new round in case of a jump, and remove local dispute from the flow.
dispute.jumped = true;
} else {
// Don't subtract 1 from length since both round arrays haven't been updated yet.
dispute.coreRoundIDToLocal[coreRoundID + 1] = dispute.rounds.length;
Round storage newRound = dispute.rounds.push();
newRound.nbVotes = core.getNumberOfVotes(_coreDisputeID);
newRound.tied = true;
}
core.appeal{value: appealCost}(_coreDisputeID, dispute.numberOfChoices, dispute.extraData);
}
if (msg.value > contribution) payable(msg.sender).send(msg.value - contribution);
}
/// @dev Allows those contributors who attempted to fund an appeal round to withdraw any reimbursable fees or rewards after the dispute gets resolved.
/// Note that withdrawals are not possible if the core contract is paused.
/// @param _coreDisputeID Index of the dispute in Kleros Core contract.
/// @param _beneficiary The address whose rewards to withdraw.
/// @param _coreRoundID The round in the Kleros Core contract the caller wants to withdraw from.
/// @param _choice The ruling option that the caller wants to withdraw from.
/// @return amount The withdrawn amount.
function withdrawFeesAndRewards(
uint256 _coreDisputeID,
address payable _beneficiary,
uint256 _coreRoundID,
uint256 _choice
) external returns (uint256 amount) {
(, , , bool isRuled, ) = core.disputes(_coreDisputeID);
require(isRuled, "Dispute should be resolved.");
require(!core.paused(), "Core is paused");
Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]];
Round storage round = dispute.rounds[dispute.coreRoundIDToLocal[_coreRoundID]];
(uint256 finalRuling, , ) = core.currentRuling(_coreDisputeID);
if (!round.hasPaid[_choice]) {
// Allow to reimburse if funding was unsuccessful for this ruling option.
amount = round.contributions[_beneficiary][_choice];
} else {
// Funding was successful for this ruling option.
if (_choice == finalRuling) {
// This ruling option is the ultimate winner.
amount = round.paidFees[_choice] > 0
? (round.contributions[_beneficiary][_choice] * round.feeRewards) / round.paidFees[_choice]
: 0;
} else if (!round.hasPaid[finalRuling]) {
// The ultimate winner was not funded in this round. In this case funded ruling option(s) are reimbursed.
amount =
(round.contributions[_beneficiary][_choice] * round.feeRewards) /
(round.paidFees[round.fundedChoices[0]] + round.paidFees[round.fundedChoices[1]]);
}
}
round.contributions[_beneficiary][_choice] = 0;
if (amount != 0) {
_beneficiary.send(amount); // Deliberate use of send to prevent reverting fallback. It's the user's responsibility to accept ETH.
emit Withdrawal(_coreDisputeID, _coreRoundID, _choice, _beneficiary, amount);
}
}
// ************************************* //
// * Public Views * //
// ************************************* //
function getFundedChoices(uint256 _coreDisputeID) public view returns (uint256[] memory fundedChoices) {
Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]];
Round storage lastRound = dispute.rounds[dispute.rounds.length - 1];
return lastRound.fundedChoices;
}
/// @dev Gets the current ruling of a specified dispute.
/// @param _coreDisputeID The ID of the dispute in Kleros Core.
/// @return ruling The current ruling.
/// @return tied Whether it's a tie or not.
/// @return overridden Whether the ruling was overridden by appeal funding or not.
function currentRuling(
uint256 _coreDisputeID
) external view override returns (uint256 ruling, bool tied, bool overridden) {
Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]];
Round storage round = dispute.rounds[dispute.rounds.length - 1];
tied = round.tied;
ruling = tied ? 0 : round.winningChoice;
(, , KlerosCore.Period period, , ) = core.disputes(_coreDisputeID);
// Override the final ruling if only one side funded the appeals.
if (period == KlerosCoreBase.Period.execution) {
uint256[] memory fundedChoices = getFundedChoices(_coreDisputeID);
if (fundedChoices.length == 1) {
ruling = fundedChoices[0];
tied = false;
overridden = true;
}
}
}
/// @dev Gets the degree of coherence of a particular voter. This function is called by Kleros Core in order to determine the amount of the reward.
/// @param _coreDisputeID The ID of the dispute in Kleros Core, not in the Dispute Kit.
/// @param _coreRoundID The ID of the round in Kleros Core, not in the Dispute Kit.
/// @param _voteID The ID of the vote.
/// @return The degree of coherence in basis points.
function getDegreeOfCoherence(
uint256 _coreDisputeID,
uint256 _coreRoundID,
uint256 _voteID,
uint256 /* _feePerJuror */,
uint256 /* _pnkAtStakePerJuror */
) external view override returns (uint256) {
// In this contract this degree can be either 0 or 1, but in other dispute kits this value can be something in between.
Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]];
Vote storage vote = dispute.rounds[dispute.coreRoundIDToLocal[_coreRoundID]].votes[_voteID];
(uint256 winningChoice, bool tied, ) = core.currentRuling(_coreDisputeID);
if (vote.voted && (vote.choice == winningChoice || tied)) {
return ONE_BASIS_POINT;
} else {
return 0;
}
}
/// @dev Gets the number of jurors who are eligible to a reward in this round.
/// @param _coreDisputeID The ID of the dispute in Kleros Core, not in the Dispute Kit.
/// @param _coreRoundID The ID of the round in Kleros Core, not in the Dispute Kit.
/// @return The number of coherent jurors.
function getCoherentCount(uint256 _coreDisputeID, uint256 _coreRoundID) external view override returns (uint256) {
Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]];
Round storage currentRound = dispute.rounds[dispute.coreRoundIDToLocal[_coreRoundID]];
(uint256 winningChoice, bool tied, ) = core.currentRuling(_coreDisputeID);
if (currentRound.totalVoted == 0 || (!tied && currentRound.counts[winningChoice] == 0)) {
return 0;
} else if (tied) {
return currentRound.totalVoted;
} else {
return currentRound.counts[winningChoice];
}
}
/// @dev Returns true if all of the jurors have cast their commits for the last round.
/// @param _coreDisputeID The ID of the dispute in Kleros Core.
/// @return Whether all of the jurors have cast their commits for the last round.
function areCommitsAllCast(uint256 _coreDisputeID) external view override returns (bool) {
Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]];
Round storage round = dispute.rounds[dispute.rounds.length - 1];
return round.totalCommitted == round.votes.length;
}
/// @dev Returns true if all of the jurors have cast their votes for the last round.
/// @param _coreDisputeID The ID of the dispute in Kleros Core.
/// @return Whether all of the jurors have cast their votes for the last round.
function areVotesAllCast(uint256 _coreDisputeID) external view override returns (bool) {
Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]];
Round storage round = dispute.rounds[dispute.rounds.length - 1];
return round.totalVoted == round.votes.length;
}
/// @dev Returns true if the specified voter was active in this round.
/// @param _coreDisputeID The ID of the dispute in Kleros Core, not in the Dispute Kit.
/// @param _coreRoundID The ID of the round in Kleros Core, not in the Dispute Kit.
/// @param _voteID The ID of the voter.
/// @return Whether the voter was active or not.
function isVoteActive(
uint256 _coreDisputeID,
uint256 _coreRoundID,
uint256 _voteID
) external view override returns (bool) {
Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]];
Vote storage vote = dispute.rounds[dispute.coreRoundIDToLocal[_coreRoundID]].votes[_voteID];
return vote.voted;
}
function getRoundInfo(
uint256 _coreDisputeID,
uint256 _coreRoundID,
uint256 _choice
)
external
view
override
returns (
uint256 winningChoice,
bool tied,
uint256 totalVoted,
uint256 totalCommited,
uint256 nbVoters,
uint256 choiceCount
)
{
Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]];
Round storage round = dispute.rounds[dispute.coreRoundIDToLocal[_coreRoundID]];
return (
round.winningChoice,
round.tied,
round.totalVoted,
round.totalCommitted,
round.votes.length,
round.counts[_choice]
);
}
function getVoteInfo(
uint256 _coreDisputeID,
uint256 _coreRoundID,
uint256 _voteID
) external view override returns (address account, bytes32 commit, uint256 choice, bool voted) {
Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]];
Vote storage vote = dispute.rounds[dispute.coreRoundIDToLocal[_coreRoundID]].votes[_voteID];
return (vote.account, vote.commit, vote.choice, vote.voted);
}
// ************************************* //
// * Internal * //
// ************************************* //
/// @dev Checks that the chosen address satisfies certain conditions for being drawn.
/// @param _coreDisputeID ID of the dispute in the core contract.
/// @param _juror Chosen address.
/// @return Whether the address can be drawn or not.
function _postDrawCheck(uint256 _coreDisputeID, address _juror) internal view returns (bool) {
(uint96 courtID, , , , ) = core.disputes(_coreDisputeID);
uint256 lockedAmountPerJuror = core
.getRoundInfo(_coreDisputeID, core.getNumberOfRounds(_coreDisputeID) - 1)
.pnkAtStakePerJuror;
(uint256 totalStaked, uint256 totalLocked, , ) = core.sortitionModule().getJurorBalance(_juror, courtID);
if (totalStaked < totalLocked + lockedAmountPerJuror) {
return false;
} else {
return _proofOfHumanity(_juror);
}
}
/// @dev Checks if an address belongs to the Proof of Humanity registry.
/// @param _address The address to check.
/// @return registered True if registered.
function _proofOfHumanity(address _address) internal view returns (bool) {
return poh.isRegistered(_address);
}
}