Skip to content
4 changes: 1 addition & 3 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,4 @@
. "$(dirname "$0")/_/husky.sh"

yarn lint-staged \
&& yarn depcheck \
&& yarn changelog \
&& git add CHANGELOG.md
&& yarn depcheck
30 changes: 0 additions & 30 deletions CHANGELOG.md

This file was deleted.

89 changes: 89 additions & 0 deletions contracts/src/kleros-v1/IKlerosLiquid.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
pragma solidity ^0.8;

import "../arbitration/IArbitrator.sol";

interface IKlerosLiquid is IArbitrator {
enum Period {
evidence, // Evidence can be submitted. This is also when drawing has to take place.
commit, // Jurors commit a hashed vote. This is skipped for courts without hidden votes.
vote, // Jurors reveal/cast their vote depending on whether the court has hidden votes or not.
appeal, // The dispute can be appealed.
execution // Tokens are redistributed and the ruling is executed.
}

enum Phase {
staking, // Stake sum trees can be updated. Pass after `minStakingTime` passes and there is at least one dispute without jurors.
generating, // Waiting for a random number. Pass as soon as it is ready.
drawing // Jurors can be drawn. Pass after all disputes have jurors or `maxDrawingTime` passes.
}

struct Dispute {
// Note that appeal `0` is equivalent to the first round of the dispute.
uint96 subcourtID; // The ID of the subcourt the dispute is in.
address arbitrated; // The arbitrated arbitrable contract.
// The number of choices jurors have when voting. This does not include choice `0` which is reserved for "refuse to arbitrate"/"no ruling".
uint256 numberOfChoices;
Period period; // The current period of the dispute.
uint256 lastPeriodChange; // The last time the period was changed.
uint256 drawsInRound; // A counter of draws made in the current round.
uint256 commitsInRound; // A counter of commits made in the current round.
bool ruled; // True if the ruling has been executed, false otherwise.
}

struct Juror {
uint256 stakedTokens; // The juror's total amount of tokens staked in subcourts.
uint256 lockedTokens; // The juror's total amount of tokens locked in disputes.
}

function phase() external view returns (Phase);

function lockInsolventTransfers() external view returns (bool);

function minStakingTime() external view returns (uint256);

function pinakion() external view returns (address);

function disputes(uint256 _index) external view returns (Dispute memory);

function jurors(address _account) external view returns (Juror memory);

function changeSubcourtTimesPerPeriod(uint96 _subcourtID, uint256[4] calldata _timesPerPeriod) external;

function executeGovernorProposal(
address _destination,
uint256 _amount,
bytes calldata _data
) external;

// Getters
function getVote(
uint256 _disputeID,
uint256 _appeal,
uint256 _voteID
)
external
view
returns (
address account,
bytes32 commit,
uint256 choice,
bool voted
);

function getDispute(uint256 _disputeID)
external
view
returns (
uint256[] memory votesLengths,
uint256[] memory tokensAtStakePerJuror,
uint256[] memory totalFeesForJurors,
uint256[] memory votesInEachRound,
uint256[] memory repartitionsInEachRound,
uint256[] memory penaltiesInEachRound
);

function getSubcourt(uint96 _subcourtID)
external
view
returns (uint256[] memory children, uint256[4] memory timesPerPeriod);
}
33 changes: 33 additions & 0 deletions contracts/src/kleros-v1/ITokenController.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
pragma solidity ^0.8;

/// @dev The token controller contract must implement these functions. See https://github.com/Giveth/minime/blob/master/contracts/TokenController.sol
interface ITokenController {
/// @notice Called when `_owner` sends ether to the MiniMe Token contract
/// @param _owner The address that sent the ether to create tokens
/// @return True if the ether is accepted, false if it throws
function proxyPayment(address _owner) external payable returns (bool);

/// @notice Notifies the controller about a token transfer allowing the
/// controller to react if desired
/// @param _from The origin of the transfer
/// @param _to The destination of the transfer
/// @param _amount The amount of the transfer
/// @return False if the controller does not authorize the transfer
function onTransfer(
address _from,
address _to,
uint256 _amount
) external returns (bool);

/// @notice Notifies the controller about an approval allowing the
/// controller to react if desired
/// @param _owner The address that calls `approve()`
/// @param _spender The spender in the `approve()` call
/// @param _amount The amount in the `approve()` call
/// @return False if the controller does not authorize the approval
function onApprove(
address _owner,
address _spender,
uint256 _amount
) external returns (bool);
}
196 changes: 196 additions & 0 deletions contracts/src/kleros-v1/KlerosV1Governor.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
pragma solidity ^0.8;

import "./IKlerosLiquid.sol";
import "./ITokenController.sol";
import "../arbitration/IArbitrable.sol";
import "../arbitration/IArbitrator.sol";

/**
* @title ERC20 interface
*/
interface IPinakion {
function balanceOf(address who) external view returns (uint256);
}

contract KlerosV1Governor is IArbitrable, ITokenController {
struct DisputeData {
uint256 klerosLiquidDisputeID;
bool ruled;
}

IArbitrator public immutable foreignGateway;
IKlerosLiquid public immutable klerosLiquid;
address public governor;

mapping(uint256 => uint256) public klerosLiquidDisputeIDtoGatewayDisputeID;
mapping(uint256 => DisputeData) public disputes; // disputes[gatewayDisputeID]
mapping(address => uint256) public frozenTokens; // frozenTokens[account] locked token which shouldn't have been blocked.
mapping(uint256 => mapping(uint256 => bool)) public isDisputeNotified; // isDisputeNotified[disputeID][roundID] used to track the notification of frozen tokens.

modifier onlyByGovernor() {
require(governor == msg.sender);
_;
}

/** @dev Constructor. Before this contract is made the new governor of KlerosLiquid, the evidence period of all subcourts has to be set to uint(-1).
* @param _klerosLiquid The trusted arbitrator to resolve potential disputes.
* @param _governor The trusted governor of the contract.
* @param _foreignGateway The trusted gateway that acts as an arbitrator, relaying disputes to v2.
*/
constructor(
IKlerosLiquid _klerosLiquid,
address _governor,
IArbitrator _foreignGateway
) {
klerosLiquid = _klerosLiquid;
governor = _governor;
foreignGateway = _foreignGateway;
}

/** @dev Lets the governor 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 calldata _data
) external onlyByGovernor {
(bool success, ) = _destination.call{value: _amount}(_data); // solium-disable-line security/no-call-value
require(success, "Call execution failed.");
}

/** @dev Changes the `governor` storage variable.
* @param _governor The new value for the `governor` storage variable.
*/
function changeGovernor(address _governor) external onlyByGovernor {
governor = _governor;
}

/** @dev Relays disputes from KlerosLiquid to Kleros v2. Only disputes in the evidence period of the initial round can be realyed.
* @param _disputeID The ID of the dispute as defined in KlerosLiquid.
*/
function relayDispute(uint256 _disputeID) external {
require(klerosLiquidDisputeIDtoGatewayDisputeID[_disputeID] == 0, "Dispute already relayed");
IKlerosLiquid.Dispute memory KlerosLiquidDispute = klerosLiquid.disputes(_disputeID);
(uint256[] memory votesLengths, , uint256[] memory totalFeesForJurors, , , ) = klerosLiquid.getDispute(
_disputeID
);

require(KlerosLiquidDispute.period == IKlerosLiquid.Period.evidence, "Invalid dispute period.");
require(votesLengths.length == 1, "Cannot relay appeals.");

klerosLiquid.executeGovernorProposal(address(this), totalFeesForJurors[0], "");

uint256 minJurors = votesLengths[0];
bytes memory extraData = abi.encode(KlerosLiquidDispute.subcourtID, minJurors);
uint256 arbitrationCost = foreignGateway.arbitrationCost(extraData);
require(totalFeesForJurors[0] >= arbitrationCost, "Fees not high enough."); // If this doesn't hold at some point, it could be a big issue.
uint256 gatewayDisputeID = foreignGateway.createDispute{value: arbitrationCost}(
KlerosLiquidDispute.numberOfChoices,
extraData
);
klerosLiquidDisputeIDtoGatewayDisputeID[_disputeID] = gatewayDisputeID;
require(gatewayDisputeID != 0, "ID must be greater than 0.");

DisputeData storage dispute = disputes[gatewayDisputeID];
dispute.klerosLiquidDisputeID = _disputeID;
}

/** @dev Give a ruling for a dispute. Can only be called by the arbitrator. TRUSTED.
* Triggers rule() from KlerosLiquid to the arbitrable contract which created the dispute.
* @param _disputeID ID of the dispute in the arbitrator contract.
* @param _ruling Ruling given by the arbitrator. Note that 0 is reserved for "Refused to arbitrate".
*/
function rule(uint256 _disputeID, uint256 _ruling) public {
require(msg.sender == address(foreignGateway), "Not the arbitrator.");
DisputeData storage dispute = disputes[_disputeID];
require(dispute.klerosLiquidDisputeID != 0, "Dispute does not exist.");
require(!dispute.ruled, "Dispute already ruled.");

dispute.ruled = true;

emit Ruling(foreignGateway, _disputeID, _ruling);

IKlerosLiquid.Dispute memory klerosLiquidDispute = klerosLiquid.disputes(dispute.klerosLiquidDisputeID);

bytes4 functionSelector = IArbitrable.rule.selector;
bytes memory data = abi.encodeWithSelector(functionSelector, dispute.klerosLiquidDisputeID, _ruling);
klerosLiquid.executeGovernorProposal(klerosLiquidDispute.arbitrated, 0, data);
}

/** @dev Registers jurors' tokens which where locked due to relaying a given dispute. These tokens don't count as locked.
* @param _disputeID The ID of the dispute as defined in KlerosLiquid.
*/
function notifyFrozenTokens(uint256 _disputeID) external {
require(klerosLiquidDisputeIDtoGatewayDisputeID[_disputeID] != 0, "Dispute not relayed.");
(uint256[] memory votesLengths, uint256[] memory tokensAtStakePerJuror, , , , ) = klerosLiquid.getDispute(
_disputeID
);

uint256 minStakingTime = klerosLiquid.minStakingTime();
IKlerosLiquid.Phase phase = klerosLiquid.phase();
bool isDrawingForbidden = phase == IKlerosLiquid.Phase.staking && minStakingTime == type(uint256).max;

for (uint256 round = 0; round < votesLengths.length; round++) {
if (isDisputeNotified[_disputeID][round]) continue;

for (uint256 voteID = 0; voteID < votesLengths[round]; voteID++) {
(address account, , , ) = klerosLiquid.getVote(_disputeID, round, voteID);
require(account != address(0x0) || isDrawingForbidden, "Juror not drawn yet.");
if (account != address(0x0)) frozenTokens[account] += tokensAtStakePerJuror[round];
}
isDisputeNotified[_disputeID][round] = true;
}
}

/** @dev Called when `_owner` sends ether to the MiniMe Token contract.
* @param _owner The address that sent the ether to create tokens.
* @return allowed Whether the operation should be allowed or not.
*/
function proxyPayment(address _owner) external payable returns (bool allowed) {
allowed = false;
}

/** @dev Notifies the controller about a token transfer allowing the controller to react if desired.
* @param _from The origin of the transfer.
* @param _to The destination of the transfer.
* @param _amount The amount of the transfer.
* @return allowed Whether the operation should be allowed or not.
*/
function onTransfer(
address _from,
address _to,
uint256 _amount
) external returns (bool allowed) {
if (klerosLiquid.lockInsolventTransfers()) {
// Never block penalties or rewards.
IPinakion pinakion = IPinakion(klerosLiquid.pinakion());
uint256 newBalance = pinakion.balanceOf(_from) - _amount; // Overflow already checked in the Minime token contract.

IKlerosLiquid.Juror memory juror = klerosLiquid.jurors(_from);

// frozenTokens <= lockedTokens always.
if (newBalance < juror.stakedTokens || newBalance < juror.lockedTokens - frozenTokens[_from]) return false;
}
allowed = true;
}

/** @dev Notifies the controller about an approval allowing the controller to react if desired.
* @param _owner The address that calls `approve()`.
* @param _spender The spender in the `approve()` call.
* @param _amount The amount in the `approve()` call.
* @return allowed Whether the operation should be allowed or not.
*/
function onApprove(
address _owner,
address _spender,
uint256 _amount
) external returns (bool allowed) {
allowed = true;
}

/// @dev This contract should be able to receive arbitration fees from KlerosLiquid.
receive() external payable {}
}