Skip to content

Commit 742aadc

Browse files
authored
Merge pull request #11 from kleros/feat/kleros-liquid-proxy
Feat/kleros liquid proxy
2 parents 8075691 + c28a9d9 commit 742aadc

File tree

3 files changed

+318
-0
lines changed

3 files changed

+318
-0
lines changed
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
pragma solidity ^0.8;
2+
3+
import "../arbitration/IArbitrator.sol";
4+
5+
interface IKlerosLiquid is IArbitrator {
6+
enum Period {
7+
evidence, // Evidence can be submitted. This is also when drawing has to take place.
8+
commit, // Jurors commit a hashed vote. This is skipped for courts without hidden votes.
9+
vote, // Jurors reveal/cast their vote depending on whether the court has hidden votes or not.
10+
appeal, // The dispute can be appealed.
11+
execution // Tokens are redistributed and the ruling is executed.
12+
}
13+
14+
enum Phase {
15+
staking, // Stake sum trees can be updated. Pass after `minStakingTime` passes and there is at least one dispute without jurors.
16+
generating, // Waiting for a random number. Pass as soon as it is ready.
17+
drawing // Jurors can be drawn. Pass after all disputes have jurors or `maxDrawingTime` passes.
18+
}
19+
20+
struct Dispute {
21+
// Note that appeal `0` is equivalent to the first round of the dispute.
22+
uint96 subcourtID; // The ID of the subcourt the dispute is in.
23+
address arbitrated; // The arbitrated arbitrable contract.
24+
// The number of choices jurors have when voting. This does not include choice `0` which is reserved for "refuse to arbitrate"/"no ruling".
25+
uint256 numberOfChoices;
26+
Period period; // The current period of the dispute.
27+
uint256 lastPeriodChange; // The last time the period was changed.
28+
uint256 drawsInRound; // A counter of draws made in the current round.
29+
uint256 commitsInRound; // A counter of commits made in the current round.
30+
bool ruled; // True if the ruling has been executed, false otherwise.
31+
}
32+
33+
struct Juror {
34+
uint256 stakedTokens; // The juror's total amount of tokens staked in subcourts.
35+
uint256 lockedTokens; // The juror's total amount of tokens locked in disputes.
36+
}
37+
38+
function phase() external view returns (Phase);
39+
40+
function lockInsolventTransfers() external view returns (bool);
41+
42+
function minStakingTime() external view returns (uint256);
43+
44+
function pinakion() external view returns (address);
45+
46+
function disputes(uint256 _index) external view returns (Dispute memory);
47+
48+
function jurors(address _account) external view returns (Juror memory);
49+
50+
function changeSubcourtTimesPerPeriod(uint96 _subcourtID, uint256[4] calldata _timesPerPeriod) external;
51+
52+
function executeGovernorProposal(
53+
address _destination,
54+
uint256 _amount,
55+
bytes calldata _data
56+
) external;
57+
58+
// Getters
59+
function getVote(
60+
uint256 _disputeID,
61+
uint256 _appeal,
62+
uint256 _voteID
63+
)
64+
external
65+
view
66+
returns (
67+
address account,
68+
bytes32 commit,
69+
uint256 choice,
70+
bool voted
71+
);
72+
73+
function getDispute(uint256 _disputeID)
74+
external
75+
view
76+
returns (
77+
uint256[] memory votesLengths,
78+
uint256[] memory tokensAtStakePerJuror,
79+
uint256[] memory totalFeesForJurors,
80+
uint256[] memory votesInEachRound,
81+
uint256[] memory repartitionsInEachRound,
82+
uint256[] memory penaltiesInEachRound
83+
);
84+
85+
function getSubcourt(uint96 _subcourtID)
86+
external
87+
view
88+
returns (uint256[] memory children, uint256[4] memory timesPerPeriod);
89+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
pragma solidity ^0.8;
2+
3+
/// @dev The token controller contract must implement these functions. See https://github.com/Giveth/minime/blob/master/contracts/TokenController.sol
4+
interface ITokenController {
5+
/// @notice Called when `_owner` sends ether to the MiniMe Token contract
6+
/// @param _owner The address that sent the ether to create tokens
7+
/// @return True if the ether is accepted, false if it throws
8+
function proxyPayment(address _owner) external payable returns (bool);
9+
10+
/// @notice Notifies the controller about a token transfer allowing the
11+
/// controller to react if desired
12+
/// @param _from The origin of the transfer
13+
/// @param _to The destination of the transfer
14+
/// @param _amount The amount of the transfer
15+
/// @return False if the controller does not authorize the transfer
16+
function onTransfer(
17+
address _from,
18+
address _to,
19+
uint256 _amount
20+
) external returns (bool);
21+
22+
/// @notice Notifies the controller about an approval allowing the
23+
/// controller to react if desired
24+
/// @param _owner The address that calls `approve()`
25+
/// @param _spender The spender in the `approve()` call
26+
/// @param _amount The amount in the `approve()` call
27+
/// @return False if the controller does not authorize the approval
28+
function onApprove(
29+
address _owner,
30+
address _spender,
31+
uint256 _amount
32+
) external returns (bool);
33+
}
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
pragma solidity ^0.8;
2+
3+
import "./IKlerosLiquid.sol";
4+
import "./ITokenController.sol";
5+
import "../arbitration/IArbitrable.sol";
6+
import "../arbitration/IArbitrator.sol";
7+
8+
/**
9+
* @title ERC20 interface
10+
*/
11+
interface IPinakion {
12+
function balanceOf(address who) external view returns (uint256);
13+
}
14+
15+
contract KlerosV1Governor is IArbitrable, ITokenController {
16+
struct DisputeData {
17+
uint256 klerosLiquidDisputeID;
18+
bool ruled;
19+
}
20+
21+
IArbitrator public immutable foreignGateway;
22+
IKlerosLiquid public immutable klerosLiquid;
23+
address public governor;
24+
25+
mapping(uint256 => uint256) public klerosLiquidDisputeIDtoGatewayDisputeID;
26+
mapping(uint256 => DisputeData) public disputes; // disputes[gatewayDisputeID]
27+
mapping(address => uint256) public frozenTokens; // frozenTokens[account] locked token which shouldn't have been blocked.
28+
mapping(uint256 => mapping(uint256 => bool)) public isDisputeNotified; // isDisputeNotified[disputeID][roundID] used to track the notification of frozen tokens.
29+
30+
modifier onlyByGovernor() {
31+
require(governor == msg.sender);
32+
_;
33+
}
34+
35+
/** @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).
36+
* @param _klerosLiquid The trusted arbitrator to resolve potential disputes.
37+
* @param _governor The trusted governor of the contract.
38+
* @param _foreignGateway The trusted gateway that acts as an arbitrator, relaying disputes to v2.
39+
*/
40+
constructor(
41+
IKlerosLiquid _klerosLiquid,
42+
address _governor,
43+
IArbitrator _foreignGateway
44+
) {
45+
klerosLiquid = _klerosLiquid;
46+
governor = _governor;
47+
foreignGateway = _foreignGateway;
48+
}
49+
50+
/** @dev Lets the governor call anything on behalf of the contract.
51+
* @param _destination The destination of the call.
52+
* @param _amount The value sent with the call.
53+
* @param _data The data sent with the call.
54+
*/
55+
function executeGovernorProposal(
56+
address _destination,
57+
uint256 _amount,
58+
bytes calldata _data
59+
) external onlyByGovernor {
60+
(bool success, ) = _destination.call{value: _amount}(_data); // solium-disable-line security/no-call-value
61+
require(success, "Call execution failed.");
62+
}
63+
64+
/** @dev Changes the `governor` storage variable.
65+
* @param _governor The new value for the `governor` storage variable.
66+
*/
67+
function changeGovernor(address _governor) external onlyByGovernor {
68+
governor = _governor;
69+
}
70+
71+
/** @dev Relays disputes from KlerosLiquid to Kleros v2. Only disputes in the evidence period of the initial round can be realyed.
72+
* @param _disputeID The ID of the dispute as defined in KlerosLiquid.
73+
*/
74+
function relayDispute(uint256 _disputeID) external {
75+
require(klerosLiquidDisputeIDtoGatewayDisputeID[_disputeID] == 0, "Dispute already relayed");
76+
IKlerosLiquid.Dispute memory KlerosLiquidDispute = klerosLiquid.disputes(_disputeID);
77+
(uint256[] memory votesLengths, , uint256[] memory totalFeesForJurors, , , ) = klerosLiquid.getDispute(
78+
_disputeID
79+
);
80+
81+
require(KlerosLiquidDispute.period == IKlerosLiquid.Period.evidence, "Invalid dispute period.");
82+
require(votesLengths.length == 1, "Cannot relay appeals.");
83+
84+
klerosLiquid.executeGovernorProposal(address(this), totalFeesForJurors[0], "");
85+
86+
uint256 minJurors = votesLengths[0];
87+
bytes memory extraData = abi.encode(KlerosLiquidDispute.subcourtID, minJurors);
88+
uint256 arbitrationCost = foreignGateway.arbitrationCost(extraData);
89+
require(totalFeesForJurors[0] >= arbitrationCost, "Fees not high enough."); // If this doesn't hold at some point, it could be a big issue.
90+
uint256 gatewayDisputeID = foreignGateway.createDispute{value: arbitrationCost}(
91+
KlerosLiquidDispute.numberOfChoices,
92+
extraData
93+
);
94+
klerosLiquidDisputeIDtoGatewayDisputeID[_disputeID] = gatewayDisputeID;
95+
require(gatewayDisputeID != 0, "ID must be greater than 0.");
96+
97+
DisputeData storage dispute = disputes[gatewayDisputeID];
98+
dispute.klerosLiquidDisputeID = _disputeID;
99+
}
100+
101+
/** @dev Give a ruling for a dispute. Can only be called by the arbitrator. TRUSTED.
102+
* Triggers rule() from KlerosLiquid to the arbitrable contract which created the dispute.
103+
* @param _disputeID ID of the dispute in the arbitrator contract.
104+
* @param _ruling Ruling given by the arbitrator. Note that 0 is reserved for "Refused to arbitrate".
105+
*/
106+
function rule(uint256 _disputeID, uint256 _ruling) public {
107+
require(msg.sender == address(foreignGateway), "Not the arbitrator.");
108+
DisputeData storage dispute = disputes[_disputeID];
109+
require(dispute.klerosLiquidDisputeID != 0, "Dispute does not exist.");
110+
require(!dispute.ruled, "Dispute already ruled.");
111+
112+
dispute.ruled = true;
113+
114+
emit Ruling(foreignGateway, _disputeID, _ruling);
115+
116+
IKlerosLiquid.Dispute memory klerosLiquidDispute = klerosLiquid.disputes(dispute.klerosLiquidDisputeID);
117+
118+
bytes4 functionSelector = IArbitrable.rule.selector;
119+
bytes memory data = abi.encodeWithSelector(functionSelector, dispute.klerosLiquidDisputeID, _ruling);
120+
klerosLiquid.executeGovernorProposal(klerosLiquidDispute.arbitrated, 0, data);
121+
}
122+
123+
/** @dev Registers jurors' tokens which where locked due to relaying a given dispute. These tokens don't count as locked.
124+
* @param _disputeID The ID of the dispute as defined in KlerosLiquid.
125+
*/
126+
function notifyFrozenTokens(uint256 _disputeID) external {
127+
require(klerosLiquidDisputeIDtoGatewayDisputeID[_disputeID] != 0, "Dispute not relayed.");
128+
(uint256[] memory votesLengths, uint256[] memory tokensAtStakePerJuror, , , , ) = klerosLiquid.getDispute(
129+
_disputeID
130+
);
131+
132+
uint256 minStakingTime = klerosLiquid.minStakingTime();
133+
IKlerosLiquid.Phase phase = klerosLiquid.phase();
134+
bool isDrawingForbidden = phase == IKlerosLiquid.Phase.staking && minStakingTime == type(uint256).max;
135+
136+
for (uint256 round = 0; round < votesLengths.length; round++) {
137+
if (isDisputeNotified[_disputeID][round]) continue;
138+
139+
for (uint256 voteID = 0; voteID < votesLengths[round]; voteID++) {
140+
(address account, , , ) = klerosLiquid.getVote(_disputeID, round, voteID);
141+
require(account != address(0x0) || isDrawingForbidden, "Juror not drawn yet.");
142+
if (account != address(0x0)) frozenTokens[account] += tokensAtStakePerJuror[round];
143+
}
144+
isDisputeNotified[_disputeID][round] = true;
145+
}
146+
}
147+
148+
/** @dev Called when `_owner` sends ether to the MiniMe Token contract.
149+
* @param _owner The address that sent the ether to create tokens.
150+
* @return allowed Whether the operation should be allowed or not.
151+
*/
152+
function proxyPayment(address _owner) external payable returns (bool allowed) {
153+
allowed = false;
154+
}
155+
156+
/** @dev Notifies the controller about a token transfer allowing the controller to react if desired.
157+
* @param _from The origin of the transfer.
158+
* @param _to The destination of the transfer.
159+
* @param _amount The amount of the transfer.
160+
* @return allowed Whether the operation should be allowed or not.
161+
*/
162+
function onTransfer(
163+
address _from,
164+
address _to,
165+
uint256 _amount
166+
) external returns (bool allowed) {
167+
if (klerosLiquid.lockInsolventTransfers()) {
168+
// Never block penalties or rewards.
169+
IPinakion pinakion = IPinakion(klerosLiquid.pinakion());
170+
uint256 newBalance = pinakion.balanceOf(_from) - _amount; // Overflow already checked in the Minime token contract.
171+
172+
IKlerosLiquid.Juror memory juror = klerosLiquid.jurors(_from);
173+
174+
// frozenTokens <= lockedTokens always.
175+
if (newBalance < juror.stakedTokens || newBalance < juror.lockedTokens - frozenTokens[_from]) return false;
176+
}
177+
allowed = true;
178+
}
179+
180+
/** @dev Notifies the controller about an approval allowing the controller to react if desired.
181+
* @param _owner The address that calls `approve()`.
182+
* @param _spender The spender in the `approve()` call.
183+
* @param _amount The amount in the `approve()` call.
184+
* @return allowed Whether the operation should be allowed or not.
185+
*/
186+
function onApprove(
187+
address _owner,
188+
address _spender,
189+
uint256 _amount
190+
) external returns (bool allowed) {
191+
allowed = true;
192+
}
193+
194+
/// @dev This contract should be able to receive arbitration fees from KlerosLiquid.
195+
receive() external payable {}
196+
}

0 commit comments

Comments
 (0)