Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 3 additions & 8 deletions contracts/foundry.toml
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@

[profile.default]
solc = "0.8.30"
evm_version = "cancun"
via_ir = true
optimizer = true
optimizer_runs = 500
optimizer_details = { yulDetails = { stackAllocation = true } }
additional_compiler_profiles = [
{ name = "tests", via_ir = false }
]
compilation_restrictions = [
{ paths = "test/foundry/KlerosCore.t.sol", via_ir = false },
]
optimizer_runs = 10000

src = 'src'
out = 'out'
libs = ['../node_modules', 'lib']
Expand Down
1 change: 1 addition & 0 deletions contracts/hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const config: HardhatUserConfig = {
{
version: "0.8.30",
settings: {
evmVersion: "cancun",
viaIR: process.env.VIA_IR !== "false", // Defaults to true
optimizer: {
enabled: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -318,19 +318,21 @@ abstract contract DisputeKitClassicBase is IDisputeKit, Initializable, UUPSProxi
if (_voteIDs.length == 0) revert EmptyVoteIDs();
if (!coreDisputeIDToActive[_coreDisputeID]) revert NotActiveForCoreDisputeID();

Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]];
uint256 localDisputeID = coreDisputeIDToLocal[_coreDisputeID];
Dispute storage dispute = disputes[localDisputeID];
if (_choice > dispute.numberOfChoices) revert ChoiceOutOfBounds();

Round storage round = dispute.rounds[dispute.rounds.length - 1];
uint256 localRoundID = dispute.rounds.length - 1;
Round storage round = dispute.rounds[localRoundID];
{
(uint96 courtID, , , , ) = core.disputes(_coreDisputeID);
(, bool hiddenVotes, , , , , ) = core.courts(courtID);
bytes32 voteHash = hashVote(_choice, _salt, _justification);
bytes32 actualVoteHash = hashVote(_choice, _salt, _justification);

// Save the votes.
for (uint256 i = 0; i < _voteIDs.length; i++) {
if (round.votes[_voteIDs[i]].account != _juror) revert JurorHasToOwnTheVote();
if (hiddenVotes && round.votes[_voteIDs[i]].commit != voteHash)
if (hiddenVotes && getExpectedVoteHash(localDisputeID, localRoundID, _voteIDs[i]) != actualVoteHash)
revert HashDoesNotMatchHiddenVoteCommitment();
if (round.votes[_voteIDs[i]].voted) revert VoteAlreadyCast();
round.votes[_voteIDs[i]].choice = _choice;
Expand Down Expand Up @@ -484,15 +486,14 @@ abstract contract DisputeKitClassicBase is IDisputeKit, Initializable, UUPSProxi
* @dev Computes the hash of a vote using ABI encoding
* @dev The unused parameters may be used by overriding contracts.
* @param _choice The choice being voted for
* @param _justification The justification for the vote
* @param _salt A random salt for commitment
* @return bytes32 The hash of the encoded vote parameters
*/
function hashVote(
uint256 _choice,
uint256 _salt,
string memory _justification
) public pure virtual returns (bytes32) {
string memory /*_justification*/
) public view virtual returns (bytes32) {
return keccak256(abi.encodePacked(_choice, _salt));
}

Expand Down Expand Up @@ -738,17 +739,29 @@ abstract contract DisputeKitClassicBase is IDisputeKit, Initializable, UUPSProxi
// * Internal * //
// ************************************* //

/// @dev Returns the expected vote hash for a given vote.
/// @param _localDisputeID The ID of the dispute in the Dispute Kit.
/// @param _localRoundID The ID of the round in the Dispute Kit.
/// @param _voteID The ID of the vote.
/// @return The expected vote hash.
function getExpectedVoteHash(
uint256 _localDisputeID,
uint256 _localRoundID,
uint256 _voteID
) internal view virtual returns (bytes32) {
return disputes[_localDisputeID].rounds[_localRoundID].votes[_voteID].commit;
}

/// @dev Checks that the chosen address satisfies certain conditions for being drawn.
/// Note that we don't check the minStake requirement here because of the implicit staking in parent courts.
/// minStake is checked directly during staking process however it's possible for the juror to get drawn
/// while having < minStake if it is later increased by governance.
/// This issue is expected and harmless.
/// @param _round The round in which the juror is being drawn.
/// @param _coreDisputeID ID of the dispute in the core contract.
/// @param _juror Chosen address.
/// @return result Whether the address passes the check or not.
function _postDrawCheck(
Round storage _round,
Round storage /*_round*/,
uint256 _coreDisputeID,
address _juror
) internal view virtual returns (bool result) {
Expand Down
72 changes: 66 additions & 6 deletions contracts/src/arbitration/dispute-kits/DisputeKitShutter.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.24;
pragma solidity ^0.8.28;

import {DisputeKitClassicBase, KlerosCore} from "./DisputeKitClassicBase.sol";

Expand All @@ -14,6 +14,19 @@ import {DisputeKitClassicBase, KlerosCore} from "./DisputeKitClassicBase.sol";
contract DisputeKitShutter is DisputeKitClassicBase {
string public constant override version = "0.13.0";

// ************************************* //
// * Storage * //
// ************************************* //

mapping(uint256 localDisputeID => mapping(uint256 localRoundID => mapping(uint256 voteID => bytes32 recoveryCommitment)))
public recoveryCommitments;

// ************************************* //
// * Transient Storage * //
// ************************************* //

bool transient callerIsJuror;

// ************************************* //
// * Events * //
// ************************************* //
Expand All @@ -22,12 +35,14 @@ contract DisputeKitShutter is DisputeKitClassicBase {
/// @param _coreDisputeID The identifier of the dispute in the Arbitrator contract.
/// @param _juror The address of the juror casting the vote commitment.
/// @param _commit The commitment hash.
/// @param _recoveryCommit The commitment hash without the justification.
/// @param _identity The Shutter identity used for encryption.
/// @param _encryptedVote The Shutter encrypted vote.
event CommitCastShutter(
uint256 indexed _coreDisputeID,
address indexed _juror,
bytes32 indexed _commit,
bytes32 _recoveryCommit,
bytes32 _identity,
bytes _encryptedVote
);
Expand Down Expand Up @@ -80,17 +95,29 @@ contract DisputeKitShutter is DisputeKitClassicBase {
/// @param _coreDisputeID The ID of the dispute in Kleros Core.
/// @param _voteIDs The IDs of the votes.
/// @param _commit The commitment hash including the justification.
/// @param _recoveryCommit The commitment hash without the justification.
/// @param _identity The Shutter identity used for encryption.
/// @param _encryptedVote The Shutter encrypted vote.
function castCommitShutter(
uint256 _coreDisputeID,
uint256[] calldata _voteIDs,
bytes32 _commit,
bytes32 _recoveryCommit,
bytes32 _identity,
bytes calldata _encryptedVote
) external notJumped(_coreDisputeID) {
if (_recoveryCommit == bytes32(0)) revert EmptyRecoveryCommit();

uint256 localDisputeID = coreDisputeIDToLocal[_coreDisputeID];
Dispute storage dispute = disputes[localDisputeID];
uint256 localRoundID = dispute.rounds.length - 1;
for (uint256 i = 0; i < _voteIDs.length; i++) {
recoveryCommitments[localDisputeID][localRoundID][_voteIDs[i]] = _recoveryCommit;
}

// `_castCommit()` ensures that the caller owns the vote
_castCommit(_coreDisputeID, _voteIDs, _commit);
emit CommitCastShutter(_coreDisputeID, msg.sender, _commit, _identity, _encryptedVote);
emit CommitCastShutter(_coreDisputeID, msg.sender, _commit, _recoveryCommit, _identity, _encryptedVote);
}

function castVoteShutter(
Expand All @@ -103,8 +130,12 @@ contract DisputeKitShutter is DisputeKitClassicBase {
Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]];
address juror = dispute.rounds[dispute.rounds.length - 1].votes[_voteIDs[0]].account;

// _castVote() ensures that all the _voteIDs do belong to `juror`
callerIsJuror = juror == msg.sender;

// `_castVote()` ensures that all the `_voteIDs` do belong to `juror`
_castVote(_coreDisputeID, _voteIDs, _choice, _salt, _justification, juror);

callerIsJuror = false;
}

// ************************************* //
Expand All @@ -122,8 +153,37 @@ contract DisputeKitShutter is DisputeKitClassicBase {
uint256 _choice,
uint256 _salt,
string memory _justification
) public pure override returns (bytes32) {
bytes32 justificationHash = keccak256(bytes(_justification));
return keccak256(abi.encode(_choice, _salt, justificationHash));
) public view override returns (bytes32) {
if (callerIsJuror) {
// Caller is the juror, hash without `_justification` to facilitate recovery.
return keccak256(abi.encodePacked(_choice, _salt));
} else {
// Caller is not the juror, hash with `_justification`.
bytes32 justificationHash = keccak256(bytes(_justification));
return keccak256(abi.encode(_choice, _salt, justificationHash));
}
}

/// @dev Returns the expected vote hash for a given vote.
/// @param _localDisputeID The ID of the dispute in the Dispute Kit.
/// @param _localRoundID The ID of the round in the Dispute Kit.
/// @param _voteID The ID of the vote.
/// @return The expected vote hash.
function getExpectedVoteHash(
uint256 _localDisputeID,
uint256 _localRoundID,
uint256 _voteID
) internal view override returns (bytes32) {
if (callerIsJuror) {
return recoveryCommitments[_localDisputeID][_localRoundID][_voteID];
} else {
return disputes[_localDisputeID].rounds[_localRoundID].votes[_voteID].commit;
}
}

// ************************************* //
// * Errors * //
// ************************************* //

error EmptyRecoveryCommit();
}
Loading