From 50a21bae8549e005cd3e8777b7b5d3bd35f2b1d0 Mon Sep 17 00:00:00 2001
From: jaybuidl
Date: Mon, 13 Dec 2021 19:34:46 +0000
Subject: [PATCH 01/14] feat: dispute kit in progress
---
...ypescript-patch-7a081096b0-bd629ad0da.zip} | Bin
CHANGELOG.md | 3 +-
.../src/arbitration/ArbitrableExample.sol | 2 +-
.../src/arbitration/DisputeKitPlurality.sol | 74 ++++++++++++++++++
.../src/arbitration/Mock/MockKlerosCore.sol | 25 ++++++
.../SortitionSumTreeFactory.sol | 37 +++++++++
contracts/typechain/index.ts | 12 +++
yarn.lock | 2 +-
8 files changed, 152 insertions(+), 3 deletions(-)
rename .yarn/cache/{typescript-patch-d95d140154-bd629ad0da.zip => typescript-patch-7a081096b0-bd629ad0da.zip} (100%)
create mode 100644 contracts/src/arbitration/DisputeKitPlurality.sol
create mode 100644 contracts/src/arbitration/Mock/MockKlerosCore.sol
create mode 100644 contracts/src/data-structures/SortitionSumTreeFactory.sol
diff --git a/.yarn/cache/typescript-patch-d95d140154-bd629ad0da.zip b/.yarn/cache/typescript-patch-7a081096b0-bd629ad0da.zip
similarity index 100%
rename from .yarn/cache/typescript-patch-d95d140154-bd629ad0da.zip
rename to .yarn/cache/typescript-patch-7a081096b0-bd629ad0da.zip
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f3bb037b4..2cf1c042b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,7 +1,8 @@
-## 0.1.0 (2021-11-30)
+## 0.1.0 (2021-12-13)
- fix(Arbitrator): memory to calldata ([4770b1f](https://github.com/kleros/kleros-v2/commit/4770b1f))
- fix(IArbitrator): appeals removed from the standard ([02c20ce](https://github.com/kleros/kleros-v2/commit/02c20ce))
+- fix(IArbitrator): change name to arbitration cost ([0ba4f29](https://github.com/kleros/kleros-v2/commit/0ba4f29))
- fix(IArbitrator): interface simplification ([e81fb8b](https://github.com/kleros/kleros-v2/commit/e81fb8b))
- fix(IArbitrator): replaced appealCost with fundingStatus ([f189dd9](https://github.com/kleros/kleros-v2/commit/f189dd9))
- feat: modern toolchain setup and simple RNG smart contracts ([17f6a76](https://github.com/kleros/kleros-v2/commit/17f6a76))
diff --git a/contracts/src/arbitration/ArbitrableExample.sol b/contracts/src/arbitration/ArbitrableExample.sol
index 757f3bb89..328ea3b13 100644
--- a/contracts/src/arbitration/ArbitrableExample.sol
+++ b/contracts/src/arbitration/ArbitrableExample.sol
@@ -6,7 +6,7 @@ import "./IArbitrable.sol";
/**
* @title ArbitrableExample
- * An example of the arbitrable contract which connects to the arbitator that implements IArbitrator interface.
+ * An example of an arbitrable contract which connects to the arbitator that implements the updated interface.
*/
contract ArbitrableExample is IArbitrable {
struct DisputeStruct {
diff --git a/contracts/src/arbitration/DisputeKitPlurality.sol b/contracts/src/arbitration/DisputeKitPlurality.sol
new file mode 100644
index 000000000..06f359a62
--- /dev/null
+++ b/contracts/src/arbitration/DisputeKitPlurality.sol
@@ -0,0 +1,74 @@
+// SPDX-License-Identifier: MIT
+
+pragma solidity ^0.8;
+
+import "./IArbitrator.sol";
+import "../rng/RNG.sol";
+import "../data-structures/SortitionSumTreeFactory.sol";
+
+contract DisputeKitPlurality {
+ // Core --> IN
+ // createDispute
+
+ // OUT -> Core
+ // report drawn jurors
+ // report ruling
+
+ // Jurors -> IN
+ // vote
+
+ // Anyone -> IN
+ // requestAppeal
+
+ // INTERNAL
+ // draw jurors <-> RNG
+ // receive evidence
+ // aggregate votes
+ // incentive (who earns how much PNK and ETH)
+ // appeal crowdfunding
+ // redistribution when ruling is final
+
+ IArbitrator public immutable core;
+ RNG public immutable rng;
+
+ using SortitionSumTreeFactory for SortitionSumTreeFactory.SortitionSumTrees; // Use library functions for sortition sum trees.
+
+ // SortitionSumTreeFactory.SortitionSumTrees internal sortitionSumTrees; // The sortition sum trees.
+
+ constructor(IArbitrator _core, RNG _rng) {
+ core = _core;
+ rng = _rng;
+ }
+
+ /**
+ * Note: disputeID is maintained by Kleros Core, not the dispute kit
+ * Note: the dispute kit does not receive any payment, Kleros Core does
+ * Note: Permissioned
+ */
+ function createDispute(
+ uint256 _disputeID,
+ uint256 _minJuror,
+ uint256 _choices,
+ bytes calldata _extraData
+ ) external {
+ require(msg.sender == address(core), "Not allowed: sender is not core");
+
+ // -- dispute specific --
+ //
+
+ // -- subcourt specific --
+ // uint minStake: min tokens required to stake on this subcourt
+ // bool hiddenVotes:
+ // uint alpha: bps of tokens lost when incoherent
+ // uint feeForJuror: paid per juror
+ // uint jurorsForCourtJump: evaluated by the appeal logic
+
+ // PROBLEM: have an interface that works for and "1 human 1 vote"
+
+ // votes = msg.value / subcourt.feeForJuror
+ // tokenAtStakePerJuror = (subcourt.minStake * subcourt.alpha) / ALPHA_DIVISOR
+ // ALPHA_DIVISOR = 10000
+ }
+
+ function drawJurors(SortitionSumTreeFactory.SortitionSumTrees calldata tree) public {}
+}
diff --git a/contracts/src/arbitration/Mock/MockKlerosCore.sol b/contracts/src/arbitration/Mock/MockKlerosCore.sol
new file mode 100644
index 000000000..63d5326ec
--- /dev/null
+++ b/contracts/src/arbitration/Mock/MockKlerosCore.sol
@@ -0,0 +1,25 @@
+// SPDX-License-Identifier: MIT
+
+pragma solidity ^0.8;
+
+import {SortitionSumTreeFactory} from "../../data-structures/SortitionSumTreeFactory.sol";
+import {DisputeKitPlurality} from "../DisputeKitPlurality.sol";
+
+contract MockKlerosCore {
+ using SortitionSumTreeFactory for SortitionSumTreeFactory.SortitionSumTrees; // Use library functions for sortition sum trees.
+ SortitionSumTreeFactory.SortitionSumTrees internal sortitionSumTrees; // The sortition sum trees.
+
+ DisputeKitPlurality disputeKit;
+
+ constructor() {
+ sortitionSumTrees.createTree(bytes32(0), 3);
+ }
+
+ // function getSortitionSumTrees() view public returns(SortitionSumTreeFactory.SortitionSumTrees calldata) {
+ // return sortitionSumTrees;
+ // }
+
+ function drawJurors(uint256 _disputeID, uint256 _iterations) public {
+ disputeKit.draw(sortitionSumTrees);
+ }
+}
diff --git a/contracts/src/data-structures/SortitionSumTreeFactory.sol b/contracts/src/data-structures/SortitionSumTreeFactory.sol
new file mode 100644
index 000000000..1d0647960
--- /dev/null
+++ b/contracts/src/data-structures/SortitionSumTreeFactory.sol
@@ -0,0 +1,37 @@
+// SPDX-License-Identifier: MIT
+
+pragma solidity ^0.8;
+
+library SortitionSumTreeFactory {
+ /* Structs */
+
+ struct SortitionSumTree {
+ uint256 K; // The maximum number of childs per node.
+ // We use this to keep track of vacant positions in the tree after removing a leaf. This is for keeping the tree as balanced as possible without spending gas on moving nodes around.
+ uint256[] stack;
+ uint256[] nodes;
+ // Two-way mapping of IDs to node indexes. Note that node index 0 is reserved for the root node, and means the ID does not have a node.
+ mapping(bytes32 => uint256) IDsToNodeIndexes;
+ mapping(uint256 => bytes32) nodeIndexesToIDs;
+ }
+
+ /* Storage */
+
+ struct SortitionSumTrees {
+ mapping(bytes32 => SortitionSumTree) sortitionSumTrees;
+ }
+
+ function createTree(
+ SortitionSumTrees storage self,
+ bytes32 _key,
+ uint256 _K
+ ) public {
+ SortitionSumTree storage tree = self.sortitionSumTrees[_key];
+ require(tree.K == 0, "Tree already exists.");
+ require(_K > 1, "K must be greater than one.");
+ tree.K = _K;
+ // tree.stack.length = 0;
+ // tree.nodes.length = 0;
+ tree.nodes.push(0);
+ }
+}
diff --git a/contracts/typechain/index.ts b/contracts/typechain/index.ts
index 76c6d659e..38e9aa183 100644
--- a/contracts/typechain/index.ts
+++ b/contracts/typechain/index.ts
@@ -1,10 +1,22 @@
/* Autogenerated file. Do not edit manually. */
/* tslint:disable */
/* eslint-disable */
+export type { ArbitrableExample } from "./ArbitrableExample";
+export type { CentralizedArbitrator } from "./CentralizedArbitrator";
+export type { DiputeKitPlurality } from "./DiputeKitPlurality";
+export type { IArbitrable } from "./IArbitrable";
+export type { IArbitrator } from "./IArbitrator";
+export type { MockKlerosCore } from "./MockKlerosCore";
export type { ConstantNG } from "./ConstantNG";
export type { IncrementalNG } from "./IncrementalNG";
export type { RNG } from "./RNG";
+export { ArbitrableExample__factory } from "./factories/ArbitrableExample__factory";
+export { CentralizedArbitrator__factory } from "./factories/CentralizedArbitrator__factory";
+export { DiputeKitPlurality__factory } from "./factories/DiputeKitPlurality__factory";
+export { IArbitrable__factory } from "./factories/IArbitrable__factory";
+export { IArbitrator__factory } from "./factories/IArbitrator__factory";
+export { MockKlerosCore__factory } from "./factories/MockKlerosCore__factory";
export { ConstantNG__factory } from "./factories/ConstantNG__factory";
export { IncrementalNG__factory } from "./factories/IncrementalNG__factory";
export { RNG__factory } from "./factories/RNG__factory";
diff --git a/yarn.lock b/yarn.lock
index be9563aa4..145d47fe0 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -14414,7 +14414,7 @@ __metadata:
"typescript@patch:typescript@^4.4.3#~builtin, typescript@patch:typescript@^4.4.4#~builtin":
version: 4.4.4
- resolution: "typescript@patch:typescript@npm%3A4.4.4#~builtin::version=4.4.4&hash=ddd1e8"
+ resolution: "typescript@patch:typescript@npm%3A4.4.4#~builtin::version=4.4.4&hash=493e53"
bin:
tsc: bin/tsc
tsserver: bin/tsserver
From 051278f0738a5a53c13c59c41e0b24a9f9187430 Mon Sep 17 00:00:00 2001
From: jaybuidl
Date: Fri, 17 Dec 2021 19:48:23 +0000
Subject: [PATCH 02/14] feat(DisputeKitPlurality): split the responsibility of
drawing jurors between Core and Dispute Kit
---
.vscode/settings.json | 2 +-
CHANGELOG.md | 25 ++++-
.../src/arbitration/CentralizedArbitrator.sol | 2 +-
.../src/arbitration/DisputeKitPlurality.sol | 98 +++++++++++++++++--
.../src/arbitration/Mock/MockKlerosCore.sol | 25 -----
.../src/arbitration/mock/MockKlerosCore.sol | 65 ++++++++++++
6 files changed, 179 insertions(+), 38 deletions(-)
delete mode 100644 contracts/src/arbitration/Mock/MockKlerosCore.sol
create mode 100644 contracts/src/arbitration/mock/MockKlerosCore.sol
diff --git a/.vscode/settings.json b/.vscode/settings.json
index edfd21471..e4f5e6c93 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,4 +1,4 @@
{
- "solidity.compileUsingRemoteVersion": "v0.8.9+commit.e5eed63a",
+ "solidity.compileUsingRemoteVersion": "v0.8.10+commit.fc410830",
"mochaExplorer.files": "contracts/test/**/*.{j,t}s"
}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d6ea88f6c..716a988ef 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,16 +1,35 @@
## 0.1.0 (2021-12-17)
+- refactor: add arbitrator data index getter ([47a1623](https://github.com/kleros/kleros-v2/commit/47a1623))
+- refactor: add evidence contracts ([09a34f3](https://github.com/kleros/kleros-v2/commit/09a34f3))
+- refactor: add interfaces + capped math ([e25b21f](https://github.com/kleros/kleros-v2/commit/e25b21f))
+- refactor: add simple evidence home contract ([1b82f62](https://github.com/kleros/kleros-v2/commit/1b82f62))
+- refactor: fix contract name ([6eb744a](https://github.com/kleros/kleros-v2/commit/6eb744a))
+- refactor: remove foreign evidence interface ([ff8c50c](https://github.com/kleros/kleros-v2/commit/ff8c50c))
+- refactor(bridge): use ArbRetryableTx#getSubmissionPrice ([61bc2f3](https://github.com/kleros/kleros-v2/commit/61bc2f3))
+- refactor(sdk): rename ([3241d10](https://github.com/kleros/kleros-v2/commit/3241d10))
+- feat: add arbitrum L1 bridge and dependencies ([b412772](https://github.com/kleros/kleros-v2/commit/b412772))
+- feat: add arbitrum L2 bridge ([457b060](https://github.com/kleros/kleros-v2/commit/457b060))
- feat: dispute kit in progress ([95b8ada](https://github.com/kleros/kleros-v2/commit/95b8ada))
- feat: modern toolchain setup and simple RNG smart contracts ([17f6a76](https://github.com/kleros/kleros-v2/commit/17f6a76))
- feat(Arbitration): standard update ([ed930de](https://github.com/kleros/kleros-v2/commit/ed930de))
+- chore: .gitignore ([0ed4d74](https://github.com/kleros/kleros-v2/commit/0ed4d74))
+- chore: .gitignore and removal of unnecessary yarn cache as we are using "zero installs" ([a6cfdd0](https://github.com/kleros/kleros-v2/commit/a6cfdd0))
+- chore: added GitHub code scanning ([4a70475](https://github.com/kleros/kleros-v2/commit/4a70475))
+- chore: added the hardhat config for layer 2 networks, added hardhat-deploy and mocha ([a12ea0e](https://github.com/kleros/kleros-v2/commit/a12ea0e))
+- chore: gitignore typechain ([b50f777](https://github.com/kleros/kleros-v2/commit/b50f777))
+- chore(typechain): clean generated files ([775ddd0](https://github.com/kleros/kleros-v2/commit/775ddd0))
+- test: add evidence contract tests ([590d800](https://github.com/kleros/kleros-v2/commit/590d800))
+- test: added a test for IncrementalNG ([65a996b](https://github.com/kleros/kleros-v2/commit/65a996b))
+- test(EvidenceModule): add test file ([9f00f98](https://github.com/kleros/kleros-v2/commit/9f00f98))
+- fix: according to evidence standard + comments ([5c95828](https://github.com/kleros/kleros-v2/commit/5c95828))
+- fix: unused code ([26b5dc3](https://github.com/kleros/kleros-v2/commit/26b5dc3))
- fix(Arbitrator): memory to calldata ([4770b1f](https://github.com/kleros/kleros-v2/commit/4770b1f))
+- fix(EvidenceModule): typos + castings + imports ([789c022](https://github.com/kleros/kleros-v2/commit/789c022))
- fix(IArbitrator): appeals removed from the standard ([02c20ce](https://github.com/kleros/kleros-v2/commit/02c20ce))
- fix(IArbitrator): change name to arbitration cost ([0ba4f29](https://github.com/kleros/kleros-v2/commit/0ba4f29))
- fix(IArbitrator): interface simplification ([e81fb8b](https://github.com/kleros/kleros-v2/commit/e81fb8b))
- fix(IArbitrator): replaced appealCost with fundingStatus ([f189dd9](https://github.com/kleros/kleros-v2/commit/f189dd9))
-- chore: added GitHub code scanning ([4a70475](https://github.com/kleros/kleros-v2/commit/4a70475))
-- chore: added the hardhat config for layer 2 networks, added hardhat-deploy and mocha ([a12ea0e](https://github.com/kleros/kleros-v2/commit/a12ea0e))
-- test: added a test for IncrementalNG ([65a996b](https://github.com/kleros/kleros-v2/commit/65a996b))
- docs: initial commit ([23356e7](https://github.com/kleros/kleros-v2/commit/23356e7))
- docs: license file added ([cb62d2c](https://github.com/kleros/kleros-v2/commit/cb62d2c))
- docs: readme and spdx headers ([8a5b397](https://github.com/kleros/kleros-v2/commit/8a5b397))
diff --git a/contracts/src/arbitration/CentralizedArbitrator.sol b/contracts/src/arbitration/CentralizedArbitrator.sol
index 0e37e8053..bee3851de 100644
--- a/contracts/src/arbitration/CentralizedArbitrator.sol
+++ b/contracts/src/arbitration/CentralizedArbitrator.sol
@@ -125,7 +125,7 @@ contract CentralizedArbitrator is IArbitrator {
uint256 _arbitrationFee,
uint256 _appealDuration,
uint256 _appealFee
- ) public {
+ ) {
arbitrationFee = _arbitrationFee;
appealDuration = _appealDuration;
appealFee = _appealFee;
diff --git a/contracts/src/arbitration/DisputeKitPlurality.sol b/contracts/src/arbitration/DisputeKitPlurality.sol
index 06f359a62..d748d37fa 100644
--- a/contracts/src/arbitration/DisputeKitPlurality.sol
+++ b/contracts/src/arbitration/DisputeKitPlurality.sol
@@ -4,7 +4,7 @@ pragma solidity ^0.8;
import "./IArbitrator.sol";
import "../rng/RNG.sol";
-import "../data-structures/SortitionSumTreeFactory.sol";
+import "./mock/MockKlerosCore.sol";
contract DisputeKitPlurality {
// Core --> IN
@@ -28,14 +28,12 @@ contract DisputeKitPlurality {
// appeal crowdfunding
// redistribution when ruling is final
- IArbitrator public immutable core;
- RNG public immutable rng;
-
- using SortitionSumTreeFactory for SortitionSumTreeFactory.SortitionSumTrees; // Use library functions for sortition sum trees.
+ // TODO: extract the necessary interfaces
+ MockKlerosCore public immutable core;
- // SortitionSumTreeFactory.SortitionSumTrees internal sortitionSumTrees; // The sortition sum trees.
+ RNG public immutable rng;
- constructor(IArbitrator _core, RNG _rng) {
+ constructor(MockKlerosCore _core, RNG _rng) {
core = _core;
rng = _rng;
}
@@ -70,5 +68,89 @@ contract DisputeKitPlurality {
// ALPHA_DIVISOR = 10000
}
- function drawJurors(SortitionSumTreeFactory.SortitionSumTrees calldata tree) public {}
+ /**
+ * @dev Draws jurors for a dispute. Can be called in parts.
+ * @param _disputeID The ID of the dispute.
+ * @param _iterations The number of iterations to run.
+ */
+ function drawJurors(uint256 _disputeID, uint256 _iterations) public {
+ uint96 subcourtID = core.getDispute(_disputeID).subcourtID;
+ bytes32 key = bytes32(bytes12(subcourtID)); // due to new conversion restrictions in v0.8
+ (
+ uint256 k, /* stack */
+ ,
+ uint256[] memory nodes
+ ) = core.getSortitionSumTree(key);
+
+ // TODO: run this only when starting the drawing period
+ uint256 randomNumber = rng.getUncorrelatedRN(block.number);
+
+ // TODO: batching with boundary checks
+ for (uint256 i = 0; i < _iterations; i++) {
+ uint256 treeIndex = draw(uint256(keccak256(abi.encodePacked(randomNumber, _disputeID, i))), k, nodes);
+ bytes32 id = core.getSortitionSumTreeID(key, treeIndex);
+ (
+ address drawnAddress, /* subcourtID */
+
+ ) = stakePathIDToAccountAndSubcourtID(id);
+
+ // TODO: Save the vote.
+ // dispute.votes[dispute.votes.length - 1][i].account = drawnAddress;
+ // jurors[drawnAddress].lockedTokens += dispute.tokensAtStakePerJuror[dispute.tokensAtStakePerJuror.length - 1];
+ // emit Draw(drawnAddress, _disputeID, dispute.votes.length - 1, i);
+
+ // TODO: Stop if dispute is fully drawn.
+ // if (i == dispute.votes[dispute.votes.length - 1].length - 1) break;
+ }
+ }
+
+ function draw(
+ uint256 _drawnNumber,
+ uint256 _k,
+ uint256[] memory _nodes
+ ) private pure returns (uint256 treeIndex) {
+ uint256 currentDrawnNumber = _drawnNumber % _nodes[0];
+ while ((_k * treeIndex) + 1 < _nodes.length) {
+ // While it still has children.
+ for (uint256 i = 1; i <= _k; i++) {
+ // Loop over children.
+ uint256 nodeIndex = (_k * treeIndex) + i;
+ uint256 nodeValue = _nodes[nodeIndex];
+
+ if (currentDrawnNumber >= nodeValue)
+ currentDrawnNumber -= nodeValue; // Go to the next child.
+ else {
+ // Pick this child.
+ treeIndex = nodeIndex;
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * @dev Unpacks a stake path ID into an account and a subcourt ID.
+ * @param _stakePathID The stake path ID to unpack.
+ * @return account The account.
+ * @return subcourtID The subcourt ID.
+ */
+ function stakePathIDToAccountAndSubcourtID(bytes32 _stakePathID)
+ internal
+ pure
+ returns (address account, uint96 subcourtID)
+ {
+ assembly {
+ // solium-disable-line security/no-inline-assembly
+ let ptr := mload(0x40)
+ for {
+ let i := 0x00
+ } lt(i, 0x14) {
+ i := add(i, 0x01)
+ } {
+ mstore8(add(add(ptr, 0x0c), i), byte(i, _stakePathID))
+ }
+ account := mload(ptr)
+ subcourtID := _stakePathID
+ }
+ }
}
diff --git a/contracts/src/arbitration/Mock/MockKlerosCore.sol b/contracts/src/arbitration/Mock/MockKlerosCore.sol
deleted file mode 100644
index 63d5326ec..000000000
--- a/contracts/src/arbitration/Mock/MockKlerosCore.sol
+++ /dev/null
@@ -1,25 +0,0 @@
-// SPDX-License-Identifier: MIT
-
-pragma solidity ^0.8;
-
-import {SortitionSumTreeFactory} from "../../data-structures/SortitionSumTreeFactory.sol";
-import {DisputeKitPlurality} from "../DisputeKitPlurality.sol";
-
-contract MockKlerosCore {
- using SortitionSumTreeFactory for SortitionSumTreeFactory.SortitionSumTrees; // Use library functions for sortition sum trees.
- SortitionSumTreeFactory.SortitionSumTrees internal sortitionSumTrees; // The sortition sum trees.
-
- DisputeKitPlurality disputeKit;
-
- constructor() {
- sortitionSumTrees.createTree(bytes32(0), 3);
- }
-
- // function getSortitionSumTrees() view public returns(SortitionSumTreeFactory.SortitionSumTrees calldata) {
- // return sortitionSumTrees;
- // }
-
- function drawJurors(uint256 _disputeID, uint256 _iterations) public {
- disputeKit.draw(sortitionSumTrees);
- }
-}
diff --git a/contracts/src/arbitration/mock/MockKlerosCore.sol b/contracts/src/arbitration/mock/MockKlerosCore.sol
new file mode 100644
index 000000000..03f2e8c54
--- /dev/null
+++ b/contracts/src/arbitration/mock/MockKlerosCore.sol
@@ -0,0 +1,65 @@
+// SPDX-License-Identifier: MIT
+
+pragma solidity ^0.8;
+
+import {SortitionSumTreeFactory} from "../../data-structures/SortitionSumTreeFactory.sol";
+// import "../DisputeKitPlurality.sol";
+import "../IArbitrable.sol";
+
+contract MockKlerosCore {
+ using SortitionSumTreeFactory for SortitionSumTreeFactory.SortitionSumTrees; // Use library functions for sortition sum trees.
+ SortitionSumTreeFactory.SortitionSumTrees internal sortitionSumTrees; // The sortition sum trees.
+
+ 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.
+ IArbitrable 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.
+ // The votes in the form `votes[appeal][voteID]`. On each round, a new list is pushed and packed with as many empty votes as there are draws. We use `dispute.votes.length` to get the number of appeals plus 1 for the first round.
+ // Vote[][] votes;
+ //VoteCounter[] voteCounters; // The vote counters in the form `voteCounters[appeal]`.
+ uint256[] tokensAtStakePerJuror; // The amount of tokens at stake for each juror in the form `tokensAtStakePerJuror[appeal]`.
+ uint256[] totalFeesForJurors; // The total juror fees paid in the form `totalFeesForJurors[appeal]`.
+ uint256 drawsInRound; // A counter of draws made in the current round.
+ uint256 commitsInRound; // A counter of commits made in the current round.
+ uint256[] votesInEachRound; // A counter of votes made in each round in the form `votesInEachRound[appeal]`.
+ // A counter of vote reward repartitions made in each round in the form `repartitionsInEachRound[appeal]`.
+ uint256[] repartitionsInEachRound;
+ uint256[] penaltiesInEachRound; // The amount of tokens collected from penalties in each round in the form `penaltiesInEachRound[appeal]`.
+ bool ruled; // True if the ruling has been executed, false otherwise.
+ }
+
+ Dispute[] public disputes;
+
+ constructor() {
+ sortitionSumTrees.createTree(bytes32(0), 3);
+ }
+
+ // DisputeKitPlurality disputeKit;
+
+ function getSortitionSumTree(bytes32 _key)
+ public
+ view
+ returns (
+ uint256 K,
+ uint256[] memory stack,
+ uint256[] memory nodes
+ )
+ {
+ SortitionSumTreeFactory.SortitionSumTree storage tree = sortitionSumTrees.sortitionSumTrees[_key];
+ K = tree.K;
+ stack = tree.stack;
+ nodes = tree.nodes;
+ }
+
+ function getSortitionSumTreeID(bytes32 _key, uint256 _nodeIndex) public view returns (bytes32 ID) {
+ ID = sortitionSumTrees.sortitionSumTrees[_key].nodeIndexesToIDs[_nodeIndex];
+ }
+
+ function getDispute(uint256 _id) public view returns (Dispute memory) {
+ return disputes[_id];
+ }
+}
From bd23000e441185bdc2d183b5dd2206a05d7a5b12 Mon Sep 17 00:00:00 2001
From: jaybuidl
Date: Sat, 18 Dec 2021 01:49:29 +0000
Subject: [PATCH 03/14] feat(DisputeKitPlurality): dispute creation
---
CHANGELOG.md | 13 ++-
.../src/arbitration/AbstractDisputeKit.sol | 31 +++++
.../src/arbitration/DisputeKitPlurality.sol | 108 +++++++++++++++---
.../src/arbitration/mock/MockKlerosCore.sol | 64 ++++++++++-
4 files changed, 193 insertions(+), 23 deletions(-)
create mode 100644 contracts/src/arbitration/AbstractDisputeKit.sol
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 716a988ef..fff36db6f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,11 @@
-## 0.1.0 (2021-12-17)
+## 0.1.0 (2021-12-18)
+- feat: add arbitrum L1 bridge and dependencies ([b412772](https://github.com/kleros/kleros-v2/commit/b412772))
+- feat: add arbitrum L2 bridge ([457b060](https://github.com/kleros/kleros-v2/commit/457b060))
+- feat: dispute kit in progress ([95b8ada](https://github.com/kleros/kleros-v2/commit/95b8ada))
+- feat: modern toolchain setup and simple RNG smart contracts ([17f6a76](https://github.com/kleros/kleros-v2/commit/17f6a76))
+- feat(Arbitration): standard update ([ed930de](https://github.com/kleros/kleros-v2/commit/ed930de))
+- feat(DisputeKitPlurality): split the responsibility of drawing jurors between Core and Dispute Kit ([f339e2b](https://github.com/kleros/kleros-v2/commit/f339e2b))
- refactor: add arbitrator data index getter ([47a1623](https://github.com/kleros/kleros-v2/commit/47a1623))
- refactor: add evidence contracts ([09a34f3](https://github.com/kleros/kleros-v2/commit/09a34f3))
- refactor: add interfaces + capped math ([e25b21f](https://github.com/kleros/kleros-v2/commit/e25b21f))
@@ -8,11 +14,6 @@
- refactor: remove foreign evidence interface ([ff8c50c](https://github.com/kleros/kleros-v2/commit/ff8c50c))
- refactor(bridge): use ArbRetryableTx#getSubmissionPrice ([61bc2f3](https://github.com/kleros/kleros-v2/commit/61bc2f3))
- refactor(sdk): rename ([3241d10](https://github.com/kleros/kleros-v2/commit/3241d10))
-- feat: add arbitrum L1 bridge and dependencies ([b412772](https://github.com/kleros/kleros-v2/commit/b412772))
-- feat: add arbitrum L2 bridge ([457b060](https://github.com/kleros/kleros-v2/commit/457b060))
-- feat: dispute kit in progress ([95b8ada](https://github.com/kleros/kleros-v2/commit/95b8ada))
-- feat: modern toolchain setup and simple RNG smart contracts ([17f6a76](https://github.com/kleros/kleros-v2/commit/17f6a76))
-- feat(Arbitration): standard update ([ed930de](https://github.com/kleros/kleros-v2/commit/ed930de))
- chore: .gitignore ([0ed4d74](https://github.com/kleros/kleros-v2/commit/0ed4d74))
- chore: .gitignore and removal of unnecessary yarn cache as we are using "zero installs" ([a6cfdd0](https://github.com/kleros/kleros-v2/commit/a6cfdd0))
- chore: added GitHub code scanning ([4a70475](https://github.com/kleros/kleros-v2/commit/4a70475))
diff --git a/contracts/src/arbitration/AbstractDisputeKit.sol b/contracts/src/arbitration/AbstractDisputeKit.sol
new file mode 100644
index 000000000..a360ff384
--- /dev/null
+++ b/contracts/src/arbitration/AbstractDisputeKit.sol
@@ -0,0 +1,31 @@
+// SPDX-License-Identifier: MIT
+
+pragma solidity ^0.8;
+
+import "./IArbitrator.sol";
+
+abstract contract AbstractDisputeKit {
+
+ /**
+ * Note: disputeID is maintained by Kleros Core, not the dispute kit
+ * Note: the dispute kit does not receive any payment, Kleros Core does
+ * Note: Permissioned
+ */
+ function createDispute(
+ uint256 _disputeID,
+ uint256 _arbitrationFee,
+ uint256 _subcourtFeeForJuror,
+ uint256 _subcourtMinStake,
+ uint256 _subcourtAlpha,
+ uint256 _choices,
+ bytes calldata _extraData
+ ) external virtual;
+
+ /**
+ * @dev Draws jurors for a dispute. Can be called in parts.
+ * @param _disputeID The ID of the dispute.
+ * @param _iterations The number of iterations to run.
+ */
+ function drawJurors(uint256 _disputeID, uint256 _iterations) external virtual;
+
+}
diff --git a/contracts/src/arbitration/DisputeKitPlurality.sol b/contracts/src/arbitration/DisputeKitPlurality.sol
index d748d37fa..f86826159 100644
--- a/contracts/src/arbitration/DisputeKitPlurality.sol
+++ b/contracts/src/arbitration/DisputeKitPlurality.sol
@@ -6,7 +6,7 @@ import "./IArbitrator.sol";
import "../rng/RNG.sol";
import "./mock/MockKlerosCore.sol";
-contract DisputeKitPlurality {
+contract DisputeKitPlurality is AbstractDisputeKit {
// Core --> IN
// createDispute
@@ -28,27 +28,86 @@ contract DisputeKitPlurality {
// appeal crowdfunding
// redistribution when ruling is final
+ // ************************ //
+ // * STRUCTS * //
+ // ************************ //
+ struct Vote {
+ address account; // The address of the juror.
+ bytes32 commit; // The commit of the juror. For courts with hidden votes.
+ uint choice; // The choice of the juror.
+ bool voted; // True if the vote has been cast or revealed, false otherwise.
+ }
+ struct VoteCounter {
+ // 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.
+ uint winningChoice;
+ mapping(uint => uint) counts; // The sum of votes for each choice in the form `counts[choice]`.
+ bool tied; // True if there is a tie, false otherwise.
+ }
+ struct Dispute {
+ //NOTE: arbitrated needed?? But it needs the chainId too, not just an address.
+ //IArbitrable arbitrated; // The address of the arbitrable contract.
+ bytes arbitratorExtraData; // Extra data for the arbitrator.
+ uint256 choices; // The number of choices the arbitrator can choose from.
+ uint256 appealPeriodStart; // Time when the appeal funding becomes possible.
+ Vote[][] votes; // The votes in the form `votes[appeal][voteID]`. On each round, a new list is pushed and packed with as many empty votes as there are draws. We use `dispute.votes.length` to get the number of appeals plus 1 for the first round.
+ VoteCounter[] voteCounters; // The vote counters in the form `voteCounters[appeal]`.
+ uint[] tokensAtStakePerJurorForRound; // The amount of tokens at stake for each juror in the form `tokensAtStakePerJuror[appeal]`.
+ uint[] arbitrationFeeForRound; // Fee paid by the arbitrable for the arbitration for each round. Must be equal or higher than arbitration cost.
+ uint drawsInCurrentRound; // A counter of draws made in the current round.
+ uint commitsInCurrentRound; // A counter of commits made in the current round.
+ uint[] votesForRound; // A counter of votes made in each round in the form `votesInEachRound[appeal]`.
+ uint[] repartitionsForRound; // A counter of vote reward repartitions made in each round in the form `repartitionsInEachRound[appeal]`.
+ uint[] penaltiesForRound; // The amount of tokens collected from penalties in each round in the form `penaltiesInEachRound[appeal]`.
+ bool ruled; // True if the ruling has been executed, false otherwise.
+ uint256 ruling; // Ruling given by the arbitrator.
+ }
+
+ struct Round {
+ mapping(uint256 => uint256) paidFees; // Tracks the fees paid for each choice in this round.
+ mapping(uint256 => bool) hasPaid; // True if this choice was fully funded, false otherwise.
+ mapping(address => mapping(uint256 => 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.
+ }
+
+ // ************************ //
+ // * STORAGE * //
+ // ************************ //
+
+ uint public constant ALPHA_DIVISOR = 1e4; // The number to divide `Court.alpha` by.
+
// TODO: extract the necessary interfaces
MockKlerosCore public immutable core;
RNG public immutable rng;
+ Dispute[] public disputes; // Stores the dispute info. disputes[disputeID].
+ mapping(uint256 => Round[]) public disputeIDtoRounds; // Maps dispute IDs to Round array that contains the info about crowdfunding.
+
+
constructor(MockKlerosCore _core, RNG _rng) {
core = _core;
rng = _rng;
}
+ // ************************ //
+ // * MODIFIERS * //
+ // ************************ //
+
/**
- * Note: disputeID is maintained by Kleros Core, not the dispute kit
- * Note: the dispute kit does not receive any payment, Kleros Core does
+ * Note: disputeID is maintained by KlerosCore, not the dispute kit
+ * Note: the dispute kit does not receive nor validate any payment, KlerosCore does
* Note: Permissioned
*/
function createDispute(
uint256 _disputeID,
- uint256 _minJuror,
+ uint256 _arbitrationFee,
+ uint256 _subcourtFeeForJuror,
+ uint256 _subcourtMinStake,
+ uint256 _subcourtAlpha,
uint256 _choices,
bytes calldata _extraData
- ) external {
+ ) external override {
require(msg.sender == address(core), "Not allowed: sender is not core");
// -- dispute specific --
@@ -61,11 +120,28 @@ contract DisputeKitPlurality {
// uint feeForJuror: paid per juror
// uint jurorsForCourtJump: evaluated by the appeal logic
- // PROBLEM: have an interface that works for and "1 human 1 vote"
+ // NOTE: the interface should work also for "1 human 1 vote"
+
+
+ Dispute storage dispute = disputes.push();
+ dispute.arbitratorExtraData = _extraData;
+ dispute.choices = _choices;
+ dispute.appealPeriodStart = 0;
- // votes = msg.value / subcourt.feeForJuror
- // tokenAtStakePerJuror = (subcourt.minStake * subcourt.alpha) / ALPHA_DIVISOR
- // ALPHA_DIVISOR = 10000
+ uint numberOfVotes = _arbitrationFee / _subcourtFeeForJuror;
+ Vote[] storage votes = dispute.votes.push(); // TODO: the array dimensions may be reversed, check!
+ while (votes.length < numberOfVotes)
+ votes.push();
+
+ dispute.voteCounters.push().tied = true;
+ dispute.tokensAtStakePerJurorForRound.push((_subcourtMinStake * _subcourtAlpha) / ALPHA_DIVISOR);
+ dispute.arbitrationFeeForRound.push(_arbitrationFee);
+ dispute.votesForRound.push(0);
+ dispute.repartitionsForRound.push(0);
+ dispute.penaltiesForRound.push(0);
+ dispute.ruling = 0;
+
+ disputeIDtoRounds[_disputeID].push();
}
/**
@@ -73,12 +149,12 @@ contract DisputeKitPlurality {
* @param _disputeID The ID of the dispute.
* @param _iterations The number of iterations to run.
*/
- function drawJurors(uint256 _disputeID, uint256 _iterations) public {
+ function drawJurors(uint256 _disputeID, uint256 _iterations) external override {
uint96 subcourtID = core.getDispute(_disputeID).subcourtID;
bytes32 key = bytes32(bytes12(subcourtID)); // due to new conversion restrictions in v0.8
(
- uint256 k, /* stack */
- ,
+ uint256 k,
+ /* stack */ ,
uint256[] memory nodes
) = core.getSortitionSumTree(key);
@@ -90,8 +166,8 @@ contract DisputeKitPlurality {
uint256 treeIndex = draw(uint256(keccak256(abi.encodePacked(randomNumber, _disputeID, i))), k, nodes);
bytes32 id = core.getSortitionSumTreeID(key, treeIndex);
(
- address drawnAddress, /* subcourtID */
-
+ address drawnAddress,
+ /* subcourtID */
) = stakePathIDToAccountAndSubcourtID(id);
// TODO: Save the vote.
@@ -104,6 +180,10 @@ contract DisputeKitPlurality {
}
}
+ // ************************ //
+ // * VIEWS * //
+ // ************************ //
+
function draw(
uint256 _drawnNumber,
uint256 _k,
diff --git a/contracts/src/arbitration/mock/MockKlerosCore.sol b/contracts/src/arbitration/mock/MockKlerosCore.sol
index 03f2e8c54..14c9b7f1e 100644
--- a/contracts/src/arbitration/mock/MockKlerosCore.sol
+++ b/contracts/src/arbitration/mock/MockKlerosCore.sol
@@ -5,6 +5,7 @@ pragma solidity ^0.8;
import {SortitionSumTreeFactory} from "../../data-structures/SortitionSumTreeFactory.sol";
// import "../DisputeKitPlurality.sol";
import "../IArbitrable.sol";
+import "../AbstractDisputeKit.sol";
contract MockKlerosCore {
using SortitionSumTreeFactory for SortitionSumTreeFactory.SortitionSumTrees; // Use library functions for sortition sum trees.
@@ -32,25 +33,63 @@ contract MockKlerosCore {
bool ruled; // True if the ruling has been executed, false otherwise.
}
+ struct Court {
+ uint96 parent; // The parent court.
+ uint[] children; // List of child courts.
+ bool hiddenVotes; // Whether to use commit and reveal or not.
+ uint minStake; // Minimum tokens needed to stake in the court.
+ uint alpha; // Basis point of tokens that are lost when incoherent.
+ uint feeForJuror; // Arbitration fee paid per juror.
+ // The appeal after the one that reaches this number of jurors will go to the parent court if any, otherwise, no more appeals are possible.
+ uint jurorsForCourtJump;
+ uint[4] timesPerPeriod; // The time allotted to each dispute period in the form `timesPerPeriod[period]`.
+ }
+
+ uint public constant MIN_JURORS = 3; // The global default minimum number of jurors in a dispute.
+
Dispute[] public disputes;
+ Court[] public courts;
+
+ AbstractDisputeKit disputeKit;
constructor() {
sortitionSumTrees.createTree(bytes32(0), 3);
}
- // DisputeKitPlurality disputeKit;
+ // TODO: only owner
+ function registerDisputeKit(AbstractDisputeKit _disputeKit) external {
+ disputeKit = _disputeKit;
+ }
+
+ function createDispute(uint256 _choices, bytes calldata _extraData) external payable returns (uint256 disputeID) {
+ (uint96 subcourtID, uint minJurors) = extraDataToSubcourtIDAndMinJurors(_extraData);
+ disputeID = disputes.length;
+
+ Court storage court = courts[0];
+
+ disputeKit.createDispute(
+ disputeID,
+ msg.value,
+ court.feeForJuror,
+ court.minStake,
+ court.alpha,
+ _choices,
+ _extraData);
+
+ //emit DisputeCreation(disputeID, IArbitrable(msg.sender));
+ }
function getSortitionSumTree(bytes32 _key)
public
view
returns (
- uint256 K,
+ uint256 k,
uint256[] memory stack,
uint256[] memory nodes
)
{
SortitionSumTreeFactory.SortitionSumTree storage tree = sortitionSumTrees.sortitionSumTrees[_key];
- K = tree.K;
+ k = tree.K;
stack = tree.stack;
nodes = tree.nodes;
}
@@ -62,4 +101,23 @@ contract MockKlerosCore {
function getDispute(uint256 _id) public view returns (Dispute memory) {
return disputes[_id];
}
+
+ /** @dev Gets a subcourt ID and the minimum number of jurors required from a specified extra data bytes array.
+ * @param _extraData The extra data bytes array. The first 32 bytes are the subcourt ID and the next 32 bytes are the minimum number of jurors.
+ * @return subcourtID The subcourt ID.
+ * @return minJurors The minimum number of jurors required.
+ */
+ function extraDataToSubcourtIDAndMinJurors(bytes memory _extraData) internal view returns (uint96 subcourtID, uint minJurors) {
+ if (_extraData.length >= 64) {
+ assembly { // solium-disable-line security/no-inline-assembly
+ subcourtID := mload(add(_extraData, 0x20))
+ minJurors := mload(add(_extraData, 0x40))
+ }
+ if (subcourtID >= courts.length) subcourtID = 0;
+ if (minJurors == 0) minJurors = MIN_JURORS;
+ } else {
+ subcourtID = 0;
+ minJurors = MIN_JURORS;
+ }
+ }
}
From dfb938e111bb4d5bbff9b108d853ebd01613b1e5 Mon Sep 17 00:00:00 2001
From: unknownunknown1
Date: Mon, 27 Dec 2021 21:35:37 +1000
Subject: [PATCH 04/14] feat(KlerosCore): add first version
---
CHANGELOG.md | 13 +-
contracts/package.json | 1 +
contracts/src/arbitration/KlerosCore.sol | 840 ++++++++++++++++++
.../arbitration/dispute-kits/DisputeKit.sol | 461 ++++++++++
.../SortitionSumTreeFactory.sol | 210 ++++-
yarn.lock | 10 +-
6 files changed, 1525 insertions(+), 10 deletions(-)
create mode 100644 contracts/src/arbitration/KlerosCore.sol
create mode 100644 contracts/src/arbitration/dispute-kits/DisputeKit.sol
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 716a988ef..d13ac938a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,11 @@
-## 0.1.0 (2021-12-17)
+## 0.1.0 (2021-12-27)
+- feat: add arbitrum L1 bridge and dependencies ([b412772](https://github.com/kleros/kleros-v2/commit/b412772))
+- feat: add arbitrum L2 bridge ([457b060](https://github.com/kleros/kleros-v2/commit/457b060))
+- feat: dispute kit in progress ([95b8ada](https://github.com/kleros/kleros-v2/commit/95b8ada))
+- feat: modern toolchain setup and simple RNG smart contracts ([17f6a76](https://github.com/kleros/kleros-v2/commit/17f6a76))
+- feat(Arbitration): standard update ([ed930de](https://github.com/kleros/kleros-v2/commit/ed930de))
+- feat(DisputeKitPlurality): split the responsibility of drawing jurors between Core and Dispute Kit ([f339e2b](https://github.com/kleros/kleros-v2/commit/f339e2b))
- refactor: add arbitrator data index getter ([47a1623](https://github.com/kleros/kleros-v2/commit/47a1623))
- refactor: add evidence contracts ([09a34f3](https://github.com/kleros/kleros-v2/commit/09a34f3))
- refactor: add interfaces + capped math ([e25b21f](https://github.com/kleros/kleros-v2/commit/e25b21f))
@@ -8,11 +14,6 @@
- refactor: remove foreign evidence interface ([ff8c50c](https://github.com/kleros/kleros-v2/commit/ff8c50c))
- refactor(bridge): use ArbRetryableTx#getSubmissionPrice ([61bc2f3](https://github.com/kleros/kleros-v2/commit/61bc2f3))
- refactor(sdk): rename ([3241d10](https://github.com/kleros/kleros-v2/commit/3241d10))
-- feat: add arbitrum L1 bridge and dependencies ([b412772](https://github.com/kleros/kleros-v2/commit/b412772))
-- feat: add arbitrum L2 bridge ([457b060](https://github.com/kleros/kleros-v2/commit/457b060))
-- feat: dispute kit in progress ([95b8ada](https://github.com/kleros/kleros-v2/commit/95b8ada))
-- feat: modern toolchain setup and simple RNG smart contracts ([17f6a76](https://github.com/kleros/kleros-v2/commit/17f6a76))
-- feat(Arbitration): standard update ([ed930de](https://github.com/kleros/kleros-v2/commit/ed930de))
- chore: .gitignore ([0ed4d74](https://github.com/kleros/kleros-v2/commit/0ed4d74))
- chore: .gitignore and removal of unnecessary yarn cache as we are using "zero installs" ([a6cfdd0](https://github.com/kleros/kleros-v2/commit/a6cfdd0))
- chore: added GitHub code scanning ([4a70475](https://github.com/kleros/kleros-v2/commit/4a70475))
diff --git a/contracts/package.json b/contracts/package.json
index dcf1cdc4f..283f2a33c 100644
--- a/contracts/package.json
+++ b/contracts/package.json
@@ -20,6 +20,7 @@
"@nomiclabs/hardhat-ethers": "^2.0.2",
"@nomiclabs/hardhat-etherscan": "^2.1.7",
"@nomiclabs/hardhat-waffle": "^2.0.1",
+ "@openzeppelin/contracts": "^4.4.1",
"@typechain/ethers-v5": "^7.2.0",
"@typechain/hardhat": "^2.3.1",
"@types/chai": "^4.2.22",
diff --git a/contracts/src/arbitration/KlerosCore.sol b/contracts/src/arbitration/KlerosCore.sol
new file mode 100644
index 000000000..a52c896e3
--- /dev/null
+++ b/contracts/src/arbitration/KlerosCore.sol
@@ -0,0 +1,840 @@
+// SPDX-License-Identifier: MIT
+
+/**
+ * @authors: [@unknownunknown1]
+ * @reviewers: []
+ * @auditors: []
+ * @bounties: []
+ * @deployments: []
+ */
+
+pragma solidity ^0.8;
+
+import "./IArbitrator.sol";
+import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
+import "./dispute-kits/DisputeKit.sol";
+import "../data-structures/SortitionSumTreeFactory.sol";
+
+/**
+ * @title KlerosCore
+ * Core module contract for KlerosCourtV2.
+ */
+contract KlerosCore is IArbitrator {
+ using SortitionSumTreeFactory for SortitionSumTreeFactory.SortitionSumTrees; // Use library functions for sortition sum trees.
+
+ /* Enums */
+
+ 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.
+ }
+
+ /* Structs */
+
+ struct Court {
+ uint96 parent; // The parent court.
+ bool hiddenVotes; // Whether to use commit and reveal or not.
+ uint256[] children; // List of child courts.
+ uint256 minStake; // Minimum tokens needed to stake in the court.
+ uint256 alpha; // Basis point of tokens that are lost when incoherent.
+ uint256 feeForJuror; // Arbitration fee paid per juror.
+ uint256 jurorsForCourtJump; // The appeal after the one that reaches this number of jurors will go to the parent court if any.
+ uint256[4] timesPerPeriod; // The time allotted to each dispute period in the form `timesPerPeriod[period]`.
+ uint256 supportedDisputeKits; // The bitfield of dispute kits that the court supports.
+ }
+
+ struct Dispute {
+ uint96 subcourtID; // The ID of the subcourt the dispute is in.
+ IArbitrable arbitrated; // The arbitrable contract.
+ DisputeKit disputeKit; // ID of the dispute kit that this dispute was assigned to.
+ Period period; // The current period of the dispute.
+ bool ruled; // True if the ruling has been executed, false otherwise.
+ uint256 currentRound; // The index of the current appeal round. Note that 0 represents the default dispute round. Former votes.length - 1.
+ uint256 lastPeriodChange; // The last time the period was changed.
+ uint256 nbVotes; // The total number of votes the dispute can possibly have in the current round. Former votes[_appeal].length.
+ mapping(uint256 => address[]) drawnJurors; // Addresses of the drawn jurors in the form `drawnJurors[_appeal]`.
+ uint256[] tokensAtStakePerJuror; // The amount of tokens at stake for each juror in the form `tokensAtStakePerJuror[appeal]`.
+ uint256[] totalFeesForJurors; // The total juror fees paid in the form `totalFeesForJurors[appeal]`.
+ uint256[] repartitionsInEachRound; // A counter of vote reward repartitions made in each round in the form `repartitionsInEachRound[appeal]`.
+ uint256[] penaltiesInEachRound; // The amount of tokens collected from penalties in each round in the form `penaltiesInEachRound[appeal]`.
+ }
+
+ struct Juror {
+ uint96[] subcourtIDs; // The IDs of subcourts where the juror's stake path ends. A stake path is a path from the forking court to a court the juror directly staked in using `_setStake`.
+ // TODO: Relations of staked and locked tokens (currently unfinished).
+ mapping(uint96 => uint256) stakedTokens; // The number of tokens the juror has staked in the subcourt in the form `stakedTokens[subcourtID]`.
+ mapping(uint96 => uint256) lockedTokens; // The number of tokens the juror has locked in the subcourt in the form `lockedTokens[subcourtID]`.
+ }
+
+ /* Constants */
+
+ uint256 public constant MAX_STAKE_PATHS = 4; // The maximum number of stake paths a juror can have.
+ uint256 public constant MIN_JURORS = 3; // The global default minimum number of jurors in a dispute.
+ uint256 public constant ALPHA_DIVISOR = 1e4; // The number to divide `Court.alpha` by.
+ uint256 public constant NON_PAYABLE_AMOUNT = (2**256 - 2) / 2; // An amount higher than the supply of ETH.
+
+ /* Storage */
+
+ address public governor; // The governor of the contract.
+ IERC20 public pinakion; // The Pinakion token contract.
+ // TODO: interactions with jurorProsecutionModule.
+ address public jurorProsecutionModule; // The module for juror's prosecution.
+
+ Court[] public courts; // The subcourts.
+
+ //TODO: disputeKits forest.
+ DisputeKit[] public disputeKits; // All supported dispute kits.
+
+ Dispute[] public disputes; // The disputes.
+ mapping(address => Juror) internal jurors; // The jurors.
+ SortitionSumTreeFactory.SortitionSumTrees internal sortitionSumTrees; // The sortition sum trees.
+
+ /* Events */
+
+ event StakeSet(address indexed _address, uint256 _subcourtID, uint256 _amount, uint256 _newTotalStake);
+ event NewPeriod(uint256 indexed _disputeID, Period _period);
+ event AppealPossible(uint256 indexed _disputeID, IArbitrable indexed _arbitrable);
+ event AppealDecision(uint256 indexed _disputeID, IArbitrable indexed _arbitrable);
+ event Draw(address indexed _address, uint256 indexed _disputeID, uint256 _appeal, uint256 _voteID);
+ event TokenAndETHShift(
+ address indexed _account,
+ uint256 indexed _disputeID,
+ int256 _tokenAmount,
+ int256 _ETHAmount
+ );
+
+ /* Modifiers */
+
+ modifier onlyByGovernor() {
+ require(governor == msg.sender);
+ _;
+ }
+
+ /** @dev Constructor.
+ * @param _governor The governor's address.
+ * @param _pinakion The address of the token contract.
+ * @param _jurorProsecutionModule The address of the juror prosecution module.
+ * @param _disputeKit The address of the default dispute kit.
+ * @param _hiddenVotes The `hiddenVotes` property value of the forking court.
+ * @param _minStake The `minStake` property value of the forking court.
+ * @param _alpha The `alpha` property value of the forking court.
+ * @param _feeForJuror The `feeForJuror` property value of the forking court.
+ * @param _jurorsForCourtJump The `jurorsForCourtJump` property value of the forking court.
+ * @param _timesPerPeriod The `timesPerPeriod` property value of the forking court.
+ * @param _sortitionSumTreeK The number of children per node of the forking court's sortition sum tree.
+ */
+ constructor(
+ address _governor,
+ IERC20 _pinakion,
+ address _jurorProsecutionModule,
+ DisputeKit _disputeKit,
+ bool _hiddenVotes,
+ uint256 _minStake,
+ uint256 _alpha,
+ uint256 _feeForJuror,
+ uint256 _jurorsForCourtJump,
+ uint256[4] memory _timesPerPeriod,
+ uint256 _sortitionSumTreeK
+ ) public {
+ governor = _governor;
+ pinakion = _pinakion;
+ jurorProsecutionModule = _jurorProsecutionModule;
+ disputeKits.push(_disputeKit);
+
+ // Create the Forking court.
+ courts.push(
+ Court({
+ parent: 0,
+ children: new uint256[](0),
+ hiddenVotes: _hiddenVotes,
+ minStake: _minStake,
+ alpha: _alpha,
+ feeForJuror: _feeForJuror,
+ jurorsForCourtJump: _jurorsForCourtJump,
+ timesPerPeriod: _timesPerPeriod,
+ supportedDisputeKits: 1 // The first bit of the bit field is supported by default.
+ })
+ );
+ sortitionSumTrees.createTree(bytes32(0), _sortitionSumTreeK);
+ }
+
+ /* External and public */
+
+ // ************************ //
+ // * Governance * //
+ // ************************ //
+
+ /** @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 `pinakion` storage variable.
+ * @param _pinakion The new value for the `pinakion` storage variable.
+ */
+ function changePinakion(IERC20 _pinakion) external onlyByGovernor {
+ pinakion = _pinakion;
+ }
+
+ /** @dev Changes the `jurorProsecutionModule` storage variable.
+ * @param _jurorProsecutionModule The new value for the `jurorProsecutionModule` storage variable.
+ */
+ function changeJurorProsecutionModule(address _jurorProsecutionModule) external onlyByGovernor {
+ jurorProsecutionModule = _jurorProsecutionModule;
+ }
+
+ /** @dev Add a new supported dispute kit module to the court.
+ * @param _disputeKitAddress The address of the dispute kit contract.
+ */
+ function addNewDisputeKit(DisputeKit _disputeKitAddress) external onlyByGovernor {
+ // TODO: the dispute kit data structure. For now keep it a simple array.
+ // Also note that in current state this function doesn't take into account that the added address is actually new.
+ require(disputeKits.length <= 256); // Can't be more than 256 because the IDs are used in a bitfield.
+ disputeKits.push(_disputeKitAddress);
+ }
+
+ /** @dev Creates a subcourt under a specified parent court.
+ * @param _parent The `parent` property value of the subcourt.
+ * @param _hiddenVotes The `hiddenVotes` property value of the subcourt.
+ * @param _minStake The `minStake` property value of the subcourt.
+ * @param _alpha The `alpha` property value of the subcourt.
+ * @param _feeForJuror The `feeForJuror` property value of the subcourt.
+ * @param _jurorsForCourtJump The `jurorsForCourtJump` property value of the subcourt.
+ * @param _timesPerPeriod The `timesPerPeriod` property value of the subcourt.
+ * @param _sortitionSumTreeK The number of children per node of the subcourt's sortition sum tree.
+ */
+ function createSubcourt(
+ uint96 _parent,
+ bool _hiddenVotes,
+ uint256 _minStake,
+ uint256 _alpha,
+ uint256 _feeForJuror,
+ uint256 _jurorsForCourtJump,
+ uint256[4] memory _timesPerPeriod,
+ uint256 _sortitionSumTreeK
+ ) external onlyByGovernor {
+ require(
+ courts[_parent].minStake <= _minStake,
+ "A subcourt cannot be a child of a subcourt with a higher minimum stake."
+ );
+
+ uint256 subcourtID = courts.length;
+ // Create the subcourt.
+ courts.push(
+ Court({
+ parent: _parent,
+ children: new uint256[](0),
+ hiddenVotes: _hiddenVotes,
+ minStake: _minStake,
+ alpha: _alpha,
+ feeForJuror: _feeForJuror,
+ jurorsForCourtJump: _jurorsForCourtJump,
+ timesPerPeriod: _timesPerPeriod,
+ supportedDisputeKits: 1
+ })
+ );
+
+ sortitionSumTrees.createTree(bytes32(subcourtID), _sortitionSumTreeK);
+ // Update the parent.
+ courts[_parent].children.push(subcourtID);
+ }
+
+ /** @dev Changes the `minStake` property value of a specified subcourt. Don't set to a value lower than its parent's `minStake` property value.
+ * @param _subcourtID The ID of the subcourt.
+ * @param _minStake The new value for the `minStake` property value.
+ */
+ function changeSubcourtMinStake(uint96 _subcourtID, uint256 _minStake) external onlyByGovernor {
+ require(_subcourtID == 0 || courts[courts[_subcourtID].parent].minStake <= _minStake);
+ for (uint256 i = 0; i < courts[_subcourtID].children.length; i++) {
+ require(
+ courts[courts[_subcourtID].children[i]].minStake >= _minStake,
+ "A subcourt cannot be the parent of a subcourt with a lower minimum stake."
+ );
+ }
+
+ courts[_subcourtID].minStake = _minStake;
+ }
+
+ /** @dev Changes the `alpha` property value of a specified subcourt.
+ * @param _subcourtID The ID of the subcourt.
+ * @param _alpha The new value for the `alpha` property value.
+ */
+ function changeSubcourtAlpha(uint96 _subcourtID, uint256 _alpha) external onlyByGovernor {
+ courts[_subcourtID].alpha = _alpha;
+ }
+
+ /** @dev Changes the `feeForJuror` property value of a specified subcourt.
+ * @param _subcourtID The ID of the subcourt.
+ * @param _feeForJuror The new value for the `feeForJuror` property value.
+ */
+ function changeSubcourtJurorFee(uint96 _subcourtID, uint256 _feeForJuror) external onlyByGovernor {
+ courts[_subcourtID].feeForJuror = _feeForJuror;
+ }
+
+ /** @dev Changes the `jurorsForCourtJump` property value of a specified subcourt.
+ * @param _subcourtID The ID of the subcourt.
+ * @param _jurorsForCourtJump The new value for the `jurorsForCourtJump` property value.
+ */
+ function changeSubcourtJurorsForJump(uint96 _subcourtID, uint256 _jurorsForCourtJump) external onlyByGovernor {
+ courts[_subcourtID].jurorsForCourtJump = _jurorsForCourtJump;
+ }
+
+ /** @dev Changes the `timesPerPeriod` property value of a specified subcourt.
+ * @param _subcourtID The ID of the subcourt.
+ * @param _timesPerPeriod The new value for the `timesPerPeriod` property value.
+ */
+ function changeSubcourtTimesPerPeriod(uint96 _subcourtID, uint256[4] memory _timesPerPeriod)
+ external
+ onlyByGovernor
+ {
+ courts[_subcourtID].timesPerPeriod = _timesPerPeriod;
+ }
+
+ /** @dev Adds/removes particular dispute kits to a subcourt's bitfield of supported dispute kits.
+ * @param _subcourtID The ID of the subcourt.
+ * @param _disputeKitIDs IDs of dispute kits in the disputeKits array, which support should be added/removed.
+ * @param _enable Whether add or remove the dispute kits from the subcourt.
+ */
+ function setDisputeKits(
+ uint96 _subcourtID,
+ uint8[] memory _disputeKitIDs,
+ bool _enable
+ ) external onlyByGovernor {
+ Court storage subcourt = courts[_subcourtID];
+ for (uint256 i = 0; i < _disputeKitIDs.length; i++) {
+ uint256 bitToChange = 1 << _disputeKitIDs[i]; // Get the bit that corresponds with dispute kit's ID.
+ if (_enable)
+ require((bitToChange & ~subcourt.supportedDisputeKits) == bitToChange, "Dispute kit already supported");
+ else require((bitToChange & subcourt.supportedDisputeKits) == bitToChange, "Dispute kit is not supported");
+
+ // Change the bit corresponding with the dispute kit's ID to an opposite value.
+ subcourt.supportedDisputeKits ^= bitToChange;
+ }
+ }
+
+ // ************************** //
+ // * General flow * //
+ // ************************** //
+
+ /** @dev Sets the caller's stake in a subcourt.
+ * @param _subcourtID The ID of the subcourt.
+ * @param _stake The new stake.
+ */
+ function setStake(uint96 _subcourtID, uint256 _stake) external {
+ _setStake(msg.sender, _subcourtID, _stake);
+ }
+
+ /** @dev Creates a dispute. Must be called by the arbitrable contract.
+ * @param _numberOfChoices Number of choices for the jurors to choose from.
+ * @param _extraData Additional info about the dispute. We use it to pass the ID of the dispute's subcourt (first 32 bytes),
+ * the minimum number of jurors required (next 32 bytes) and the ID of the specific dispute kit (last 32 bytes).
+ * @return disputeID The ID of the created dispute.
+ */
+ function createDispute(uint256 _numberOfChoices, bytes memory _extraData)
+ external
+ payable
+ override
+ returns (uint256 disputeID)
+ {
+ require(msg.value >= arbitrationCost(_extraData), "Not enough ETH to cover arbitration cost.");
+ (uint96 subcourtID, , uint8 disputeKitID) = extraDataToSubcourtIDMinJurorsDisputeKit(_extraData);
+
+ uint256 bitToCheck = 1 << disputeKitID; // Get the bit that corresponds with dispute kit's ID.
+ require(
+ (bitToCheck & courts[subcourtID].supportedDisputeKits) == bitToCheck,
+ "The dispute kit is not supported by this subcourt"
+ );
+
+ disputeID = disputes.length;
+ Dispute storage dispute = disputes.push();
+ dispute.subcourtID = subcourtID;
+ dispute.arbitrated = IArbitrable(msg.sender);
+
+ DisputeKit disputeKit = disputeKits[disputeKitID];
+ dispute.disputeKit = disputeKit;
+
+ dispute.lastPeriodChange = block.timestamp;
+ dispute.nbVotes = msg.value / courts[dispute.subcourtID].feeForJuror;
+
+ dispute.tokensAtStakePerJuror.push(
+ (courts[dispute.subcourtID].minStake * courts[dispute.subcourtID].alpha) / ALPHA_DIVISOR
+ );
+ dispute.totalFeesForJurors.push(msg.value);
+ dispute.repartitionsInEachRound.push(0);
+ dispute.penaltiesInEachRound.push(0);
+
+ disputeKit.createDispute(disputeID, _numberOfChoices);
+ emit DisputeCreation(disputeID, IArbitrable(msg.sender));
+ }
+
+ /** @dev Passes the period of a specified dispute.
+ * @param _disputeID The ID of the dispute.
+ */
+ function passPeriod(uint256 _disputeID) external {
+ Dispute storage dispute = disputes[_disputeID];
+ if (dispute.period == Period.evidence) {
+ require(
+ dispute.currentRound > 0 ||
+ block.timestamp - dispute.lastPeriodChange >=
+ courts[dispute.subcourtID].timesPerPeriod[uint256(dispute.period)],
+ "The evidence period time has not passed yet and it is not an appeal."
+ );
+ require(
+ dispute.drawnJurors[dispute.currentRound].length == dispute.nbVotes,
+ "The dispute has not finished drawing yet."
+ );
+ dispute.period = courts[dispute.subcourtID].hiddenVotes ? Period.commit : Period.vote;
+ } else if (dispute.period == Period.commit) {
+ // In case the jurors finished casting commits beforehand the dispute kit should call passPeriod() by itself.
+ require(
+ block.timestamp - dispute.lastPeriodChange >=
+ courts[dispute.subcourtID].timesPerPeriod[uint256(dispute.period)] ||
+ msg.sender == address(dispute.disputeKit),
+ "The commit period time has not passed yet."
+ );
+ dispute.period = Period.vote;
+ } else if (dispute.period == Period.vote) {
+ // In case the jurors finished casting votes beforehand the dispute kit should call passPeriod() by itself.
+ require(
+ block.timestamp - dispute.lastPeriodChange >=
+ courts[dispute.subcourtID].timesPerPeriod[uint256(dispute.period)] ||
+ msg.sender == address(dispute.disputeKit),
+ "The vote period time has not passed yet"
+ );
+ dispute.period = Period.appeal;
+ emit AppealPossible(_disputeID, dispute.arbitrated);
+ } else if (dispute.period == Period.appeal) {
+ require(
+ block.timestamp - dispute.lastPeriodChange >=
+ courts[dispute.subcourtID].timesPerPeriod[uint256(dispute.period)],
+ "The appeal period time has not passed yet."
+ );
+ dispute.period = Period.execution;
+ } else if (dispute.period == Period.execution) {
+ revert("The dispute is already in the last period.");
+ }
+
+ dispute.lastPeriodChange = block.timestamp;
+ emit NewPeriod(_disputeID, dispute.period);
+ }
+
+ /** @dev Draws jurors for the dispute. Can be called in parts.
+ * @param _disputeID The ID of the dispute.
+ * @param _iterations The number of iterations to run.
+ */
+ function draw(uint256 _disputeID, uint256 _iterations) external {
+ Dispute storage dispute = disputes[_disputeID];
+ require(dispute.period == Period.evidence, "Should be evidence period.");
+ DisputeKit disputeKit = dispute.disputeKit;
+ uint256 startIndex = dispute.drawnJurors[dispute.currentRound].length;
+ uint256 endIndex = startIndex + _iterations <= dispute.nbVotes ? startIndex + _iterations : dispute.nbVotes;
+
+ for (uint256 i = startIndex; i < endIndex; i++) {
+ address drawnAddress = disputeKit.draw(_disputeID);
+ if (drawnAddress != address(0)) {
+ // In case no one has staked at the court yet.
+ jurors[drawnAddress].lockedTokens[dispute.subcourtID] += dispute.tokensAtStakePerJuror[
+ dispute.currentRound
+ ];
+ dispute.drawnJurors[dispute.currentRound].push(drawnAddress);
+ emit Draw(drawnAddress, _disputeID, dispute.currentRound, i);
+ }
+ }
+ }
+
+ /** @dev Appeals the ruling of a specified dispute.
+ * @param _disputeID The ID of the dispute.
+ */
+ function appeal(uint256 _disputeID) external payable {
+ require(msg.value >= appealCost(_disputeID), "Not enough ETH to cover appeal cost.");
+
+ Dispute storage dispute = disputes[_disputeID];
+ require(dispute.period == Period.appeal, "Dispute is not appealable");
+ require(msg.sender == address(dispute.disputeKit), "Can only be called by the dispute kit.");
+
+ if (dispute.nbVotes >= courts[dispute.subcourtID].jurorsForCourtJump)
+ // Jump to parent subcourt.
+ // TODO: Handle court jump in the Forking court. Also make sure the new subcourt is compatible with the dispute kit.
+ dispute.subcourtID = courts[dispute.subcourtID].parent;
+
+ dispute.period = Period.evidence;
+ dispute.lastPeriodChange = block.timestamp;
+ // As many votes that can be afforded by the provided funds.
+ dispute.nbVotes = msg.value / courts[dispute.subcourtID].feeForJuror;
+
+ dispute.tokensAtStakePerJuror.push(
+ (courts[dispute.subcourtID].minStake * courts[dispute.subcourtID].alpha) / ALPHA_DIVISOR
+ );
+ dispute.totalFeesForJurors.push(msg.value);
+ dispute.repartitionsInEachRound.push(0);
+ dispute.penaltiesInEachRound.push(0);
+
+ dispute.currentRound++;
+
+ emit AppealDecision(_disputeID, dispute.arbitrated);
+ emit NewPeriod(_disputeID, Period.evidence);
+ }
+
+ /** @dev Distribute tokens and ETH for the specific round of the dispute. Can be called in parts.
+ * @param _disputeID The ID of the dispute.
+ * @param _appeal The appeal round.
+ * @param _iterations The number of iterations to run.
+ */
+ function execute(
+ uint256 _disputeID,
+ uint256 _appeal,
+ uint256 _iterations
+ ) external {
+ Dispute storage dispute = disputes[_disputeID];
+ require(dispute.period == Period.execution, "Should be execution period.");
+
+ uint256 end = dispute.repartitionsInEachRound[_appeal] + _iterations;
+ uint256 penaltiesInRoundCache = dispute.penaltiesInEachRound[_appeal]; // For saving gas.
+
+ uint256 numberOfVotesInRound = dispute.drawnJurors[_appeal].length;
+ uint256 coherentCount = dispute.disputeKit.getCoherentCount(_disputeID, _appeal); // Total number of jurors that are eligible to a reward in this round.
+
+ address account; // Address of the juror.
+ uint256 degreeOfCoherence; // [0, 1] value that determines how coherent the juror was in this round, in basis points.
+
+ if (coherentCount == 0) {
+ // We loop over the votes once as there are no rewards because it is not a tie and no one in this round is coherent with the final outcome.
+ if (end > numberOfVotesInRound) end = numberOfVotesInRound;
+ } else {
+ // We loop over the votes twice, first to collect penalties, and second to distribute them as rewards along with arbitration fees.
+ if (end > numberOfVotesInRound * 2) end = numberOfVotesInRound * 2;
+ }
+
+ for (uint256 i = dispute.repartitionsInEachRound[_appeal]; i < end; i++) {
+ // Penalty.
+ if (i < numberOfVotesInRound) {
+ degreeOfCoherence = dispute.disputeKit.getDegreeOfCoherence(_disputeID, _appeal, i);
+ if (degreeOfCoherence > ALPHA_DIVISOR) degreeOfCoherence = ALPHA_DIVISOR; // Make sure the degree doesn't exceed 1, though it should be ensured by the dispute kit.
+
+ uint256 penalty = (dispute.tokensAtStakePerJuror[_appeal] * (ALPHA_DIVISOR - degreeOfCoherence)) /
+ ALPHA_DIVISOR; // Fully coherent jurors won't be penalized.
+ penaltiesInRoundCache += penalty;
+
+ account = dispute.drawnJurors[_appeal][i];
+ jurors[account].lockedTokens[dispute.subcourtID] -= penalty; // Release this part of locked tokens.
+ // TODO: properly update staked tokens in case of penalty.
+
+ // Unstake the juror if he lost due to inactivity.
+ if (!dispute.disputeKit.isVoteActive(_disputeID, _appeal, i)) {
+ for (uint256 j = 0; j < jurors[account].subcourtIDs.length; j++)
+ _setStake(account, jurors[account].subcourtIDs[j], 0);
+ }
+ emit TokenAndETHShift(account, _disputeID, -int256(penalty), 0);
+
+ if (i == numberOfVotesInRound - 1) {
+ if (coherentCount == 0) {
+ // No one was coherent. Send the rewards to governor.
+ payable(governor).send(dispute.totalFeesForJurors[_appeal]);
+ pinakion.transfer(governor, penaltiesInRoundCache);
+ }
+ }
+ // Reward.
+ } else {
+ degreeOfCoherence = dispute.disputeKit.getDegreeOfCoherence(
+ _disputeID,
+ _appeal,
+ i % numberOfVotesInRound
+ );
+ if (degreeOfCoherence > ALPHA_DIVISOR) degreeOfCoherence = ALPHA_DIVISOR;
+ account = dispute.drawnJurors[_appeal][i % 2];
+ // Release the rest of the tokens of the juror for this round.
+ jurors[account].lockedTokens[dispute.subcourtID] -=
+ (dispute.tokensAtStakePerJuror[_appeal] * degreeOfCoherence) /
+ ALPHA_DIVISOR;
+ // TODO: properly update staked tokens in case of reward.
+
+ uint256 tokenReward = ((penaltiesInRoundCache / coherentCount) * degreeOfCoherence) / ALPHA_DIVISOR;
+ uint256 ETHReward = ((dispute.totalFeesForJurors[_appeal] / coherentCount) * degreeOfCoherence) /
+ ALPHA_DIVISOR;
+
+ pinakion.transfer(account, tokenReward);
+ payable(account).send(ETHReward);
+ emit TokenAndETHShift(account, _disputeID, int256(tokenReward), int256(ETHReward));
+ }
+ }
+
+ if (dispute.penaltiesInEachRound[_appeal] != penaltiesInRoundCache)
+ dispute.penaltiesInEachRound[_appeal] = penaltiesInRoundCache;
+ dispute.repartitionsInEachRound[_appeal] = end;
+ }
+
+ /** @dev Executes a specified dispute's ruling. UNTRUSTED.
+ * @param _disputeID The ID of the dispute.
+ */
+ function executeRuling(uint256 _disputeID) external {
+ Dispute storage dispute = disputes[_disputeID];
+ require(dispute.period == Period.execution, "Should be execution period.");
+ require(!dispute.ruled, "Ruling already executed.");
+
+ uint256 winningChoice = currentRuling(_disputeID);
+ dispute.ruled = true;
+ dispute.arbitrated.rule(_disputeID, winningChoice);
+ }
+
+ // ********************* //
+ // * Getters * //
+ // ********************* //
+
+ /** @dev Gets the cost of arbitration in a specified subcourt.
+ * @param _extraData Additional info about the dispute. We use it to pass the ID of the subcourt to create the dispute in (first 32 bytes)
+ * and the minimum number of jurors required (next 32 bytes).
+ * @return cost The arbitration cost.
+ */
+ function arbitrationCost(bytes memory _extraData) public view override returns (uint256 cost) {
+ (uint96 subcourtID, uint256 minJurors, ) = extraDataToSubcourtIDMinJurorsDisputeKit(_extraData);
+ cost = courts[subcourtID].feeForJuror * minJurors;
+ }
+
+ /** @dev Gets the cost of appealing a specified dispute.
+ * @param _disputeID The ID of the dispute.
+ * @return cost The appeal cost.
+ */
+ function appealCost(uint256 _disputeID) public view returns (uint256 cost) {
+ Dispute storage dispute = disputes[_disputeID];
+ if (dispute.nbVotes >= courts[dispute.subcourtID].jurorsForCourtJump) {
+ // Jump to parent subcourt.
+ if (dispute.subcourtID == 0)
+ // Already in the forking court.
+ cost = NON_PAYABLE_AMOUNT; // Get the cost of the parent subcourt.
+ else cost = courts[courts[dispute.subcourtID].parent].feeForJuror * ((dispute.nbVotes * 2) + 1);
+ }
+ // Stay in current subcourt.
+ else cost = courts[dispute.subcourtID].feeForJuror * ((dispute.nbVotes * 2) + 1);
+ }
+
+ /** @dev Gets the start and the end of a specified dispute's current appeal period.
+ * @param _disputeID The ID of the dispute.
+ * @return start The start of the appeal period.
+ * @return end The end of the appeal period.
+ */
+ function appealPeriod(uint256 _disputeID) public view returns (uint256 start, uint256 end) {
+ Dispute storage dispute = disputes[_disputeID];
+ if (dispute.period == Period.appeal) {
+ start = dispute.lastPeriodChange;
+ end = dispute.lastPeriodChange + courts[dispute.subcourtID].timesPerPeriod[uint256(Period.appeal)];
+ } else {
+ start = 0;
+ end = 0;
+ }
+ }
+
+ /** @dev Gets the current ruling of a specified dispute.
+ * @param _disputeID The ID of the dispute.
+ * @return ruling The current ruling.
+ */
+ function currentRuling(uint256 _disputeID) public view returns (uint256 ruling) {
+ DisputeKit disputeKit = disputes[_disputeID].disputeKit;
+ return disputeKit.currentRuling(_disputeID);
+ }
+
+ // ************************************* //
+ // * Getters for dispute kits * //
+ // ************************************* //
+
+ function getSortitionSumTree(bytes32 _key)
+ public
+ view
+ returns (
+ uint256 K,
+ uint256[] memory stack,
+ uint256[] memory nodes
+ )
+ {
+ SortitionSumTreeFactory.SortitionSumTree storage tree = sortitionSumTrees.sortitionSumTrees[_key];
+ K = tree.K;
+ stack = tree.stack;
+ nodes = tree.nodes;
+ }
+
+ function getSortitionSumTreeID(bytes32 _key, uint256 _nodeIndex) external view returns (bytes32 ID) {
+ ID = sortitionSumTrees.sortitionSumTrees[_key].nodeIndexesToIDs[_nodeIndex];
+ }
+
+ function getSubcourtID(uint256 _disputeID) external view returns (uint256 subcourtID) {
+ return disputes[_disputeID].subcourtID;
+ }
+
+ function getCurrentPeriod(uint256 _disputeID) external view returns (Period period) {
+ return disputes[_disputeID].period;
+ }
+
+ function areVotesHidden(uint256 _subcourtID) external view returns (bool hiddenVotes) {
+ return courts[_subcourtID].hiddenVotes;
+ }
+
+ function isRuled(uint256 _disputeID) external view returns (bool) {
+ return disputes[_disputeID].ruled;
+ }
+
+ /* Internal */
+
+ /** @dev Sets the specified juror's stake in a subcourt.
+ * `O(n + p * log_k(j))` where
+ * `n` is the number of subcourts the juror has staked in,
+ * `p` is the depth of the subcourt tree,
+ * `k` is the minimum number of children per node of one of these subcourts' sortition sum tree,
+ * and `j` is the maximum number of jurors that ever staked in one of these subcourts simultaneously.
+ * @param _account The address of the juror.
+ * @param _subcourtID The ID of the subcourt.
+ * @param _stake The new stake.
+ */
+ function _setStake(
+ address _account,
+ uint96 _subcourtID,
+ uint256 _stake
+ ) internal {
+ Juror storage juror = jurors[_account];
+ bytes32 stakePathID = accountAndSubcourtIDToStakePathID(_account, _subcourtID);
+ uint256 currentStake = sortitionSumTrees.stakeOf(bytes32(uint256(_subcourtID)), stakePathID);
+
+ if (_stake != 0) {
+ require(_stake >= courts[_subcourtID].minStake, "Should stake at least minimal amount");
+ if (currentStake == 0) {
+ require(juror.subcourtIDs.length < MAX_STAKE_PATHS, "Max stake paths reached");
+ juror.subcourtIDs.push(_subcourtID);
+ }
+ } else {
+ for (uint256 i = 0; i < juror.subcourtIDs.length; i++) {
+ if (juror.subcourtIDs[i] == _subcourtID) {
+ juror.subcourtIDs[i] = juror.subcourtIDs[juror.subcourtIDs.length - 1];
+ juror.subcourtIDs.pop();
+ break;
+ }
+ }
+ }
+
+ // Update juror's records.
+ uint256 newTotalStake = juror.stakedTokens[_subcourtID] - currentStake + _stake;
+ juror.stakedTokens[_subcourtID] = newTotalStake;
+
+ // Update subcourt parents.
+ bool finished = false;
+ uint256 currentSubcourtID = _subcourtID;
+ while (!finished) {
+ sortitionSumTrees.set(bytes32(currentSubcourtID), _stake, stakePathID);
+ if (currentSubcourtID == 0) finished = true;
+ else currentSubcourtID = courts[currentSubcourtID].parent;
+ }
+
+ if (_stake >= currentStake) {
+ require(
+ pinakion.transferFrom(_account, address(this), _stake - currentStake),
+ "Sender doesn't have enough tokens"
+ );
+ } else {
+ // Keep locked tokens in the contract and release them after dispute is executed. Note that the current flow of staking is unfinished.
+ require(
+ pinakion.transfer(_account, currentStake - _stake - juror.lockedTokens[_subcourtID]),
+ "The transfer failed"
+ );
+ }
+
+ emit StakeSet(_account, _subcourtID, _stake, newTotalStake);
+ }
+
+ /** @dev Gets a subcourt ID, the minimum number of jurors and an ID of a dispute kit from a specified extra data bytes array.
+ * Note that if extradata contains an incorrect value then this value will be switched to default.
+ * @param _extraData The extra data bytes array. The first 32 bytes are the subcourt ID, the next are the minimum number of jurors and the last are the dispute kit ID.
+ * @return subcourtID The subcourt ID.
+ * @return minJurors The minimum number of jurors required.
+ * @return disputeKitID The ID of the dispute kit.
+ */
+ function extraDataToSubcourtIDMinJurorsDisputeKit(bytes memory _extraData)
+ internal
+ view
+ returns (
+ uint96 subcourtID,
+ uint256 minJurors,
+ uint8 disputeKitID
+ )
+ {
+ // Note that if the extradata doesn't contain 32 bytes for the dispute kit ID it'll return the default 0 index.
+ if (_extraData.length >= 64) {
+ assembly {
+ // solium-disable-line security/no-inline-assembly
+ subcourtID := mload(add(_extraData, 0x20))
+ minJurors := mload(add(_extraData, 0x40))
+ disputeKitID := mload(add(_extraData, 0x60))
+ }
+ if (subcourtID >= courts.length) subcourtID = 0;
+ if (minJurors == 0) minJurors = MIN_JURORS;
+ if (disputeKitID >= disputeKits.length) disputeKitID = 0;
+ } else {
+ subcourtID = 0;
+ minJurors = MIN_JURORS;
+ disputeKitID = 0;
+ }
+ }
+
+ /** @dev Packs an account and a subcourt ID into a stake path ID.
+ * @param _account The address of the juror to pack.
+ * @param _subcourtID The subcourt ID to pack.
+ * @return stakePathID The stake path ID.
+ */
+ function accountAndSubcourtIDToStakePathID(address _account, uint96 _subcourtID)
+ internal
+ pure
+ returns (bytes32 stakePathID)
+ {
+ assembly {
+ // solium-disable-line security/no-inline-assembly
+ let ptr := mload(0x40)
+ for {
+ let i := 0x00
+ } lt(i, 0x14) {
+ i := add(i, 0x01)
+ } {
+ mstore8(add(ptr, i), byte(add(0x0c, i), _account))
+ }
+ for {
+ let i := 0x14
+ } lt(i, 0x20) {
+ i := add(i, 0x01)
+ } {
+ mstore8(add(ptr, i), byte(i, _subcourtID))
+ }
+ stakePathID := mload(ptr)
+ }
+ }
+
+ function getDispute(uint256 _disputeID)
+ external
+ view
+ returns (
+ uint256[] memory tokensAtStakePerJuror,
+ uint256[] memory totalFeesForJurors,
+ uint256[] memory repartitionsInEachRound,
+ uint256[] memory penaltiesInEachRound
+ )
+ {
+ Dispute storage dispute = disputes[_disputeID];
+ tokensAtStakePerJuror = dispute.tokensAtStakePerJuror;
+ totalFeesForJurors = dispute.totalFeesForJurors;
+ repartitionsInEachRound = dispute.repartitionsInEachRound;
+ penaltiesInEachRound = dispute.penaltiesInEachRound;
+ }
+}
diff --git a/contracts/src/arbitration/dispute-kits/DisputeKit.sol b/contracts/src/arbitration/dispute-kits/DisputeKit.sol
new file mode 100644
index 000000000..5b3a62b9e
--- /dev/null
+++ b/contracts/src/arbitration/dispute-kits/DisputeKit.sol
@@ -0,0 +1,461 @@
+// SPDX-License-Identifier: MIT
+
+pragma solidity ^0.8;
+
+import "../IArbitrator.sol";
+import "../KlerosCore.sol";
+
+//import "./ChainlinkRNG.sol";
+
+contract DisputeKit {
+ 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 MULTIPLIER_DIVISOR = 10000; // The divisor parameter for multipliers.
+
+ 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".
+ }
+
+ 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 => uint256) paidFees; // Tracks the fees paid for each choice in this round.
+ mapping(uint256 => bool) hasPaid; // True if this choice was fully funded, false otherwise.
+ mapping(address => mapping(uint256 => 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.
+ }
+
+ 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.
+ }
+
+ KlerosCore public immutable klerosCore;
+ 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.
+
+ uint256 public constant RN = 5; // Mock random number for the drawing process, to temporarily replace the RNG module.
+
+ event Contribution(
+ uint256 indexed _disputeID,
+ uint256 indexed _round,
+ uint256 _choice,
+ address indexed _contributor,
+ uint256 _amount
+ );
+
+ event Withdrawal(
+ uint256 indexed _disputeID,
+ uint256 indexed _round,
+ uint256 _choice,
+ address indexed _contributor,
+ uint256 _amount
+ );
+
+ event ChoiceFunded(uint256 indexed _disputeID, uint256 indexed _round, uint256 indexed _choice);
+
+ constructor(KlerosCore _klerosCore) public {
+ klerosCore = _klerosCore;
+ }
+
+ /** @dev Creates a local dispute and maps it to the dispute ID in the Core contract.
+ * @param _disputeID The ID of the dispute in Kleros Core.
+ * @param _numberOfChoices Number of choices of the disute
+ */
+ function createDispute(uint256 _disputeID, uint256 _numberOfChoices) external {
+ require(msg.sender == address(klerosCore), "Can only be called by Kleros Core.");
+ uint256 localDisputeID = disputes.length;
+ Dispute storage dispute = disputes.push();
+ dispute.numberOfChoices = _numberOfChoices;
+
+ Round storage round = dispute.rounds.push();
+ round.tied = true;
+
+ coreDisputeIDToLocal[_disputeID] = localDisputeID;
+ }
+
+ /** @dev Draws the juror from the sortition tree. The drawn address is picked up by Kleros Core.
+ * @param _disputeID The ID of the dispute in Kleros Core.
+ * @return drawnAddress The drawn address.
+ */
+ function draw(uint256 _disputeID) external returns (address drawnAddress) {
+ require(msg.sender == address(klerosCore), "Can only be called by Kleros Core.");
+ bytes32 key = bytes32(klerosCore.getSubcourtID(_disputeID)); // Get the ID of the tree.
+ uint256 drawnNumber = getRandomNumber();
+
+ (uint256 K, , uint256[] memory nodes) = klerosCore.getSortitionSumTree(key);
+ uint256 treeIndex = 0;
+ uint256 currentDrawnNumber = drawnNumber % nodes[0];
+
+ Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]];
+ Round storage round = dispute.rounds[dispute.rounds.length - 1];
+
+ // TODO: Handle the situation when no one has staked yet.
+ while (
+ (K * treeIndex) + 1 < nodes.length // While it still has children.
+ )
+ for (uint256 i = 1; i <= K; i++) {
+ // Loop over children.
+ uint256 nodeIndex = (K * treeIndex) + i;
+ uint256 nodeValue = nodes[nodeIndex];
+
+ if (currentDrawnNumber >= nodeValue)
+ currentDrawnNumber -= nodeValue; // Go to the next child.
+ else {
+ // Pick this child.
+ treeIndex = nodeIndex;
+ break;
+ }
+ }
+
+ bytes32 ID = klerosCore.getSortitionSumTreeID(key, treeIndex);
+ drawnAddress = stakePathIDToAccount(ID);
+
+ round.votes.push(Vote({account: drawnAddress, commit: bytes32(0), choice: 0, voted: false}));
+ }
+
+ /** @dev Mock RNG function.
+ */
+ function getRandomNumber() public view returns (uint256) {
+ // return RNG.getRN(rngRequestId);
+ return RN;
+ }
+
+ /** @dev Sets the caller's commit for the specified votes.
+ * `O(n)` where
+ * `n` is the number of votes.
+ * @param _disputeID The ID of the dispute.
+ * @param _voteIDs The IDs of the votes.
+ * @param _commit The commit.
+ */
+ function castCommit(
+ uint256 _disputeID,
+ uint256[] calldata _voteIDs,
+ bytes32 _commit
+ ) external {
+ require(
+ klerosCore.getCurrentPeriod(_disputeID) == KlerosCore.Period.commit,
+ "The dispute should be in Commit period."
+ );
+ require(_commit != bytes32(0), "Empty commit.");
+
+ Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]];
+ 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.");
+ require(round.votes[_voteIDs[i]].commit == bytes32(0), "Already committed this vote.");
+ round.votes[_voteIDs[i]].commit = _commit;
+ }
+ round.totalCommitted += _voteIDs.length;
+
+ if (round.totalCommitted == round.votes.length) klerosCore.passPeriod(_disputeID);
+ }
+
+ /** @dev Sets the caller's choices for the specified votes.
+ * `O(n)` where
+ * `n` is the number of votes.
+ * @param _disputeID The ID of the dispute.
+ * @param _voteIDs The IDs of the votes.
+ * @param _choice The choice.
+ * @param _salt The salt for the commit if the votes were hidden.
+ */
+ function castVote(
+ uint256 _disputeID,
+ uint256[] calldata _voteIDs,
+ uint256 _choice,
+ uint256 _salt
+ ) external {
+ require(
+ klerosCore.getCurrentPeriod(_disputeID) == KlerosCore.Period.vote,
+ "The dispute should be in Vote period."
+ );
+ require(_voteIDs.length > 0);
+
+ Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]];
+ require(_choice <= dispute.numberOfChoices, "Choice out of bounds");
+
+ Round storage round = dispute.rounds[dispute.rounds.length - 1];
+ bool hiddenVotes = klerosCore.areVotesHidden(klerosCore.getSubcourtID(_disputeID));
+
+ // 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 subcourts 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;
+ }
+ }
+
+ // Automatically switch period when voting is finished.
+ if (round.totalVoted == round.votes.length) klerosCore.passPeriod(_disputeID);
+ }
+
+ /** @dev Manages contributions, and appeals a dispute if at least two choices are fully funded.
+ * Note that the surplus deposit will be reimbursed.
+ * @param _disputeID Index of the dispute in Kleros Core contract.
+ * @param _choice A choice that receives funding.
+ */
+ function fundAppeal(uint256 _disputeID, uint256 _choice) external payable {
+ Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]];
+ require(_choice <= dispute.numberOfChoices, "There is no such ruling to fund.");
+
+ (uint256 appealPeriodStart, uint256 appealPeriodEnd) = klerosCore.appealPeriod(_disputeID);
+ require(block.timestamp >= appealPeriodStart && block.timestamp < appealPeriodEnd, "Appeal period is over.");
+
+ uint256 multiplier;
+ if (currentRuling(_disputeID) == _choice) {
+ multiplier = WINNER_STAKE_MULTIPLIER;
+ } else {
+ require(
+ block.timestamp - appealPeriodStart <
+ ((appealPeriodEnd - appealPeriodStart) * LOSER_APPEAL_PERIOD_MULTIPLIER) / MULTIPLIER_DIVISOR,
+ "Appeal period is over for loser"
+ );
+ multiplier = LOSER_STAKE_MULTIPLIER;
+ }
+
+ Round storage round = dispute.rounds[dispute.rounds.length - 1];
+ require(!round.hasPaid[_choice], "Appeal fee is already paid.");
+ uint256 appealCost = klerosCore.appealCost(_disputeID);
+ uint256 totalCost = appealCost + (appealCost * multiplier) / MULTIPLIER_DIVISOR;
+
+ // 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(_disputeID, dispute.rounds.length - 1, _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(_disputeID, dispute.rounds.length - 1, _choice);
+ }
+
+ if (round.fundedChoices.length > 1) {
+ // At least two sides are fully funded.
+ round.feeRewards = round.feeRewards - appealCost;
+
+ Round storage newRound = dispute.rounds.push();
+ newRound.tied = true;
+ klerosCore.appeal{value: appealCost}(_disputeID);
+ }
+
+ if (msg.value > contribution) payable(msg.sender).send(msg.value - contribution);
+ }
+
+ /** @dev Allows to withdraw any reimbursable fees or rewards after the dispute gets resolved.
+ * @param _disputeID Index of the dispute in Kleros Core contract.
+ * @param _beneficiary The address whose rewards to withdraw.
+ * @param _round The round 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 _disputeID,
+ address payable _beneficiary,
+ uint256 _round,
+ uint256 _choice
+ ) external returns (uint256 amount) {
+ require(klerosCore.isRuled(_disputeID), "Dispute should be resolved.");
+
+ Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]];
+ Round storage round = dispute.rounds[_round];
+ uint256 finalRuling = currentRuling(_disputeID);
+
+ 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(_disputeID, _round, _choice, _beneficiary, amount);
+ }
+ }
+
+ /* Public views */
+
+ /** @dev Gets the current ruling of a specified dispute.
+ * @param _disputeID The ID of the dispute in Kleros Core.
+ * @return ruling The current ruling.
+ */
+ function currentRuling(uint256 _disputeID) public view returns (uint256 ruling) {
+ Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]];
+ Round storage round = dispute.rounds[dispute.rounds.length - 1];
+ ruling = round.tied ? 0 : round.winningChoice;
+ }
+
+ /** @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 _disputeID The ID of the dispute in Kleros Core.
+ * @param _round The ID of the round.
+ * @param _voteID The ID of the vote.
+ * @return The degree of coherence in basis points.
+ */
+ function getDegreeOfCoherence(
+ uint256 _disputeID,
+ uint256 _round,
+ uint256 _voteID
+ ) external view 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[_disputeID]];
+ Round storage lastRound = dispute.rounds[dispute.rounds.length - 1];
+ Vote storage vote = dispute.rounds[_round].votes[_voteID];
+
+ if (vote.voted && (vote.choice == lastRound.winningChoice || lastRound.tied)) {
+ return 10000; // 1 in basis points.
+ } else {
+ return 0;
+ }
+ }
+
+ /** @dev Gets the number of jurors who are eligible to a reward in this round.
+ * @param _disputeID The ID of the dispute in Kleros Core.
+ * @param _round The ID of the round.
+ * @return The number of coherent jurors.
+ */
+ function getCoherentCount(uint256 _disputeID, uint256 _round) external view returns (uint256) {
+ Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]];
+ Round storage lastRound = dispute.rounds[dispute.rounds.length - 1];
+ Round storage currentRound = dispute.rounds[_round];
+ uint256 winningChoice = lastRound.winningChoice;
+
+ if (currentRound.totalVoted == 0 || (!lastRound.tied && currentRound.counts[winningChoice] == 0)) {
+ return 0;
+ } else if (lastRound.tied) {
+ return currentRound.totalVoted;
+ } else {
+ return currentRound.counts[winningChoice];
+ }
+ }
+
+ /** @dev Returns true if the specified voter was active in this round.
+ * @param _disputeID The ID of the dispute in Kleros Core.
+ * @param _round The ID of the round.
+ * @param _voteID The ID of the voter.
+ * @return Whether the voter was active or not.
+ */
+ function isVoteActive(
+ uint256 _disputeID,
+ uint256 _round,
+ uint256 _voteID
+ ) external view returns (bool) {
+ Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]];
+ Vote storage vote = dispute.rounds[_round].votes[_voteID];
+ return vote.voted;
+ }
+
+ /** @dev Retrieves a juror's address from the stake path ID.
+ * @param _stakePathID The stake path ID to unpack.
+ * @return account The account.
+ */
+ function stakePathIDToAccount(bytes32 _stakePathID) internal pure returns (address account) {
+ assembly {
+ // solium-disable-line security/no-inline-assembly
+ let ptr := mload(0x40)
+ for {
+ let i := 0x00
+ } lt(i, 0x14) {
+ i := add(i, 0x01)
+ } {
+ mstore8(add(add(ptr, 0x0c), i), byte(i, _stakePathID))
+ }
+ account := mload(ptr)
+ }
+ }
+
+ function getRoundInfo(
+ uint256 _disputeID,
+ uint256 _round,
+ uint256 _choice
+ )
+ external
+ view
+ returns (
+ uint256 winningChoice,
+ bool tied,
+ uint256 totalVoted,
+ uint256 totalCommited,
+ uint256 nbVoters,
+ uint256 choiceCount
+ )
+ {
+ Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]];
+ Round storage round = dispute.rounds[_round];
+ return (
+ round.winningChoice,
+ round.tied,
+ round.totalVoted,
+ round.totalCommitted,
+ round.votes.length,
+ round.counts[_choice]
+ );
+ }
+
+ function getVoteInfo(
+ uint256 _disputeID,
+ uint256 _round,
+ uint256 _voteID
+ )
+ external
+ view
+ returns (
+ address account,
+ bytes32 commit,
+ uint256 choice,
+ bool voted
+ )
+ {
+ Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]];
+ Vote storage vote = dispute.rounds[_round].votes[_voteID];
+ return (vote.account, vote.commit, vote.choice, vote.voted);
+ }
+}
diff --git a/contracts/src/data-structures/SortitionSumTreeFactory.sol b/contracts/src/data-structures/SortitionSumTreeFactory.sol
index 1d0647960..4c58af45b 100644
--- a/contracts/src/data-structures/SortitionSumTreeFactory.sol
+++ b/contracts/src/data-structures/SortitionSumTreeFactory.sol
@@ -1,7 +1,19 @@
// SPDX-License-Identifier: MIT
+/**
+ * @authors: [@epiqueras, @unknownunknown1]
+ * @reviewers: []
+ * @auditors: []
+ * @bounties: []
+ * @deployments: []
+ */
+
pragma solidity ^0.8;
+/**
+ * @title SortitionSumTreeFactory
+ * @dev A factory of trees that keeps track of staked values for sortition. This is the updated version for 0.8 compiler.
+ */
library SortitionSumTreeFactory {
/* Structs */
@@ -21,17 +33,209 @@ library SortitionSumTreeFactory {
mapping(bytes32 => SortitionSumTree) sortitionSumTrees;
}
+ /* Public */
+
+ /**
+ * @dev Create a sortition sum tree at the specified key.
+ * @param _key The key of the new tree.
+ * @param _K The number of children each node in the tree should have.
+ */
function createTree(
SortitionSumTrees storage self,
bytes32 _key,
uint256 _K
- ) public {
+ ) external {
SortitionSumTree storage tree = self.sortitionSumTrees[_key];
require(tree.K == 0, "Tree already exists.");
require(_K > 1, "K must be greater than one.");
tree.K = _K;
- // tree.stack.length = 0;
- // tree.nodes.length = 0;
tree.nodes.push(0);
}
+
+ /**
+ * @dev Set a value of a tree.
+ * @param _key The key of the tree.
+ * @param _value The new value.
+ * @param _ID The ID of the value.
+ * `O(log_k(n))` where
+ * `k` is the maximum number of childs per node in the tree,
+ * and `n` is the maximum number of nodes ever appended.
+ */
+ function set(
+ SortitionSumTrees storage self,
+ bytes32 _key,
+ uint256 _value,
+ bytes32 _ID
+ ) external {
+ SortitionSumTree storage tree = self.sortitionSumTrees[_key];
+ uint256 treeIndex = tree.IDsToNodeIndexes[_ID];
+
+ if (treeIndex == 0) {
+ // No existing node.
+ if (_value != 0) {
+ // Non zero value.
+ // Append.
+ // Add node.
+ if (tree.stack.length == 0) {
+ // No vacant spots.
+ // Get the index and append the value.
+ treeIndex = tree.nodes.length;
+ tree.nodes.push(_value);
+
+ // Potentially append a new node and make the parent a sum node.
+ if (treeIndex != 1 && (treeIndex - 1) % tree.K == 0) {
+ // Is first child.
+ uint256 parentIndex = treeIndex / tree.K;
+ bytes32 parentID = tree.nodeIndexesToIDs[parentIndex];
+ uint256 newIndex = treeIndex + 1;
+ tree.nodes.push(tree.nodes[parentIndex]);
+ delete tree.nodeIndexesToIDs[parentIndex];
+ tree.IDsToNodeIndexes[parentID] = newIndex;
+ tree.nodeIndexesToIDs[newIndex] = parentID;
+ }
+ } else {
+ // Some vacant spot.
+ // Pop the stack and append the value.
+ treeIndex = tree.stack[tree.stack.length - 1];
+ tree.stack.pop();
+ tree.nodes[treeIndex] = _value;
+ }
+
+ // Add label.
+ tree.IDsToNodeIndexes[_ID] = treeIndex;
+ tree.nodeIndexesToIDs[treeIndex] = _ID;
+
+ updateParents(self, _key, treeIndex, true, _value);
+ }
+ } else {
+ // Existing node.
+ if (_value == 0) {
+ // Zero value.
+ // Remove.
+ // Remember value and set to 0.
+ uint256 value = tree.nodes[treeIndex];
+ tree.nodes[treeIndex] = 0;
+
+ // Push to stack.
+ tree.stack.push(treeIndex);
+
+ // Clear label.
+ delete tree.IDsToNodeIndexes[_ID];
+ delete tree.nodeIndexesToIDs[treeIndex];
+
+ updateParents(self, _key, treeIndex, false, value);
+ } else if (_value != tree.nodes[treeIndex]) {
+ // New, non zero value.
+ // Set.
+ bool plusOrMinus = tree.nodes[treeIndex] <= _value;
+ uint256 plusOrMinusValue = plusOrMinus
+ ? _value - tree.nodes[treeIndex]
+ : tree.nodes[treeIndex] - _value;
+ tree.nodes[treeIndex] = _value;
+
+ updateParents(self, _key, treeIndex, plusOrMinus, plusOrMinusValue);
+ }
+ }
+ }
+
+ /* Public Views */
+
+ /**
+ * @dev Query the leaves of a tree. Note that if `startIndex == 0`, the tree is empty and the root node will be returned.
+ * @param _key The key of the tree to get the leaves from.
+ * @param _cursor The pagination cursor.
+ * @param _count The number of items to return.
+ * @return startIndex The index at which leaves start.
+ * @return values The values of the returned leaves.
+ * @return hasMore Whether there are more for pagination.
+ * `O(n)` where
+ * `n` is the maximum number of nodes ever appended.
+ */
+ function queryLeafs(
+ SortitionSumTrees storage self,
+ bytes32 _key,
+ uint256 _cursor,
+ uint256 _count
+ )
+ external
+ view
+ returns (
+ uint256 startIndex,
+ uint256[] memory values,
+ bool hasMore
+ )
+ {
+ SortitionSumTree storage tree = self.sortitionSumTrees[_key];
+
+ // Find the start index.
+ for (uint256 i = 0; i < tree.nodes.length; i++) {
+ if ((tree.K * i) + 1 >= tree.nodes.length) {
+ startIndex = i;
+ break;
+ }
+ }
+
+ // Get the values.
+ uint256 loopStartIndex = startIndex + _cursor;
+ values = new uint256[](
+ loopStartIndex + _count > tree.nodes.length ? tree.nodes.length - loopStartIndex : _count
+ );
+ uint256 valuesIndex = 0;
+ for (uint256 j = loopStartIndex; j < tree.nodes.length; j++) {
+ if (valuesIndex < _count) {
+ values[valuesIndex] = tree.nodes[j];
+ valuesIndex++;
+ } else {
+ hasMore = true;
+ break;
+ }
+ }
+ }
+
+ /** @dev Gets a specified ID's associated value.
+ * @param _key The key of the tree.
+ * @param _ID The ID of the value.
+ * @return value The associated value.
+ */
+ function stakeOf(
+ SortitionSumTrees storage self,
+ bytes32 _key,
+ bytes32 _ID
+ ) external view returns (uint256 value) {
+ SortitionSumTree storage tree = self.sortitionSumTrees[_key];
+ uint256 treeIndex = tree.IDsToNodeIndexes[_ID];
+
+ if (treeIndex == 0) value = 0;
+ else value = tree.nodes[treeIndex];
+ }
+
+ /* Private */
+
+ /**
+ * @dev Update all the parents of a node.
+ * @param _key The key of the tree to update.
+ * @param _treeIndex The index of the node to start from.
+ * @param _plusOrMinus Whether to add (true) or substract (false).
+ * @param _value The value to add or substract.
+ * `O(log_k(n))` where
+ * `k` is the maximum number of childs per node in the tree,
+ * and `n` is the maximum number of nodes ever appended.
+ */
+ function updateParents(
+ SortitionSumTrees storage self,
+ bytes32 _key,
+ uint256 _treeIndex,
+ bool _plusOrMinus,
+ uint256 _value
+ ) private {
+ SortitionSumTree storage tree = self.sortitionSumTrees[_key];
+
+ uint256 parentIndex = _treeIndex;
+ while (parentIndex != 0) {
+ parentIndex = (parentIndex - 1) / tree.K;
+ tree.nodes[parentIndex] = _plusOrMinus
+ ? tree.nodes[parentIndex] + _value
+ : tree.nodes[parentIndex] - _value;
+ }
+ }
}
diff --git a/yarn.lock b/yarn.lock
index 145d47fe0..4a54991a6 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -995,6 +995,7 @@ __metadata:
"@nomiclabs/hardhat-ethers": ^2.0.2
"@nomiclabs/hardhat-etherscan": ^2.1.7
"@nomiclabs/hardhat-waffle": ^2.0.1
+ "@openzeppelin/contracts": ^4.4.1
"@typechain/ethers-v5": ^7.2.0
"@typechain/hardhat": ^2.3.1
"@types/chai": ^4.2.22
@@ -1109,6 +1110,13 @@ __metadata:
languageName: node
linkType: hard
+"@openzeppelin/contracts@npm:^4.4.1":
+ version: 4.4.1
+ resolution: "@openzeppelin/contracts@npm:4.4.1"
+ checksum: 87ee7ebe8e6493fc4d657b05c5ddb5f254b73bf55d80c25a790f8e74087b6d90c0a6ec504fe45949c27501f73bca7d64129aad29488707a2c9b5afdb035966df
+ languageName: node
+ linkType: hard
+
"@resolver-engine/core@npm:^0.3.3":
version: 0.3.3
resolution: "@resolver-engine/core@npm:0.3.3"
@@ -14414,7 +14422,7 @@ __metadata:
"typescript@patch:typescript@^4.4.3#~builtin, typescript@patch:typescript@^4.4.4#~builtin":
version: 4.4.4
- resolution: "typescript@patch:typescript@npm%3A4.4.4#~builtin::version=4.4.4&hash=493e53"
+ resolution: "typescript@patch:typescript@npm%3A4.4.4#~builtin::version=4.4.4&hash=ddd1e8"
bin:
tsc: bin/tsc
tsserver: bin/tsserver
From 399acebfd4d12ef86ab00034d1da8d718659e2f2 Mon Sep 17 00:00:00 2001
From: jaybuidl
Date: Tue, 28 Dec 2021 11:58:37 +0000
Subject: [PATCH 05/14] test(DisputeKitPlurality): added basic test, work in
progress
---
CHANGELOG.md | 3 +-
contracts/hardhat.config.ts | 31 ++++++++-
contracts/package.json | 4 +-
.../src/arbitration/DisputeKitPlurality.sol | 23 ++++---
.../src/arbitration/mock/MockKlerosCore.sol | 48 +++++++++++---
contracts/test/arbitration/index.ts | 63 +++++++++++++++++++
6 files changed, 152 insertions(+), 20 deletions(-)
create mode 100644 contracts/test/arbitration/index.ts
diff --git a/CHANGELOG.md b/CHANGELOG.md
index fff36db6f..44f02c71b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,10 +1,11 @@
-## 0.1.0 (2021-12-18)
+## 0.1.0 (2021-12-28)
- feat: add arbitrum L1 bridge and dependencies ([b412772](https://github.com/kleros/kleros-v2/commit/b412772))
- feat: add arbitrum L2 bridge ([457b060](https://github.com/kleros/kleros-v2/commit/457b060))
- feat: dispute kit in progress ([95b8ada](https://github.com/kleros/kleros-v2/commit/95b8ada))
- feat: modern toolchain setup and simple RNG smart contracts ([17f6a76](https://github.com/kleros/kleros-v2/commit/17f6a76))
- feat(Arbitration): standard update ([ed930de](https://github.com/kleros/kleros-v2/commit/ed930de))
+- feat(DisputeKitPlurality): dispute creation ([6a2490e](https://github.com/kleros/kleros-v2/commit/6a2490e))
- feat(DisputeKitPlurality): split the responsibility of drawing jurors between Core and Dispute Kit ([f339e2b](https://github.com/kleros/kleros-v2/commit/f339e2b))
- refactor: add arbitrator data index getter ([47a1623](https://github.com/kleros/kleros-v2/commit/47a1623))
- refactor: add evidence contracts ([09a34f3](https://github.com/kleros/kleros-v2/commit/09a34f3))
diff --git a/contracts/hardhat.config.ts b/contracts/hardhat.config.ts
index fbd66b057..c781cd9a1 100644
--- a/contracts/hardhat.config.ts
+++ b/contracts/hardhat.config.ts
@@ -6,8 +6,9 @@ import "@nomiclabs/hardhat-waffle";
import "@typechain/hardhat";
import "hardhat-gas-reporter";
import "solidity-coverage";
-import "hardhat-deploy"
-import "hardhat-deploy-ethers"
+import "hardhat-deploy";
+import "hardhat-deploy-ethers";
+import "hardhat-watcher";
dotenv.config();
@@ -43,6 +44,18 @@ const config: HardhatUserConfig = {
saveDeployments: false,
tags: ["test", "local"],
},
+ mainnetFork: {
+ url: `http://127.0.0.1:8545`,
+ chainId: 1,
+ forking: {
+ url: `https://mainnet.infura.io/v3/${process.env.INFURA_API_KEY}`,
+ },
+ accounts: process.env.MAINNET_PRIVATE_KEY !== undefined ? [process.env.MAINNET_PRIVATE_KEY] : [],
+ live: false,
+ saveDeployments: false,
+ tags: ["test", "local"],
+ },
+
// Home chain ---------------------------------------------------------------------------------
arbitrumRinkeby: {
chainId: 421611,
@@ -101,6 +114,20 @@ const config: HardhatUserConfig = {
etherscan: {
apiKey: process.env.ETHERSCAN_API_KEY,
},
+ watcher: {
+ compilation: {
+ tasks: ["compile"],
+ files: ["./contracts"],
+ verbose: true,
+ },
+ testArbitration: {
+ tasks: [
+ { command: "compile", params: { quiet: true } },
+ { command: "test", params: { noCompile: true, testFiles: ["./test/arbitration/index.ts"] } },
+ ],
+ files: ["./test/**/*", "./src/**/*"],
+ },
+ },
};
export default config;
diff --git a/contracts/package.json b/contracts/package.json
index dcf1cdc4f..9bceaf320 100644
--- a/contracts/package.json
+++ b/contracts/package.json
@@ -14,7 +14,8 @@
"build": "hardhat compile",
"clean": "hardhat clean",
"deploy": "hardhat deploy",
- "test": "hardhat test"
+ "test": "hardhat test",
+ "watch": "hardhat watch"
},
"devDependencies": {
"@nomiclabs/hardhat-ethers": "^2.0.2",
@@ -36,6 +37,7 @@
"hardhat-deploy": "^0.9.6",
"hardhat-deploy-ethers": "^0.3.0-beta.11",
"hardhat-gas-reporter": "^1.0.4",
+ "hardhat-watcher": "^2.1.1",
"mocha": "^9.1.3",
"solhint": "^3.3.6",
"solidity-coverage": "^0.7.17",
diff --git a/contracts/src/arbitration/DisputeKitPlurality.sol b/contracts/src/arbitration/DisputeKitPlurality.sol
index f86826159..940118ef0 100644
--- a/contracts/src/arbitration/DisputeKitPlurality.sol
+++ b/contracts/src/arbitration/DisputeKitPlurality.sol
@@ -84,7 +84,6 @@ contract DisputeKitPlurality is AbstractDisputeKit {
Dispute[] public disputes; // Stores the dispute info. disputes[disputeID].
mapping(uint256 => Round[]) public disputeIDtoRounds; // Maps dispute IDs to Round array that contains the info about crowdfunding.
-
constructor(MockKlerosCore _core, RNG _rng) {
core = _core;
rng = _rng;
@@ -120,9 +119,6 @@ contract DisputeKitPlurality is AbstractDisputeKit {
// uint feeForJuror: paid per juror
// uint jurorsForCourtJump: evaluated by the appeal logic
- // NOTE: the interface should work also for "1 human 1 vote"
-
-
Dispute storage dispute = disputes.push();
dispute.arbitratorExtraData = _extraData;
dispute.choices = _choices;
@@ -141,7 +137,19 @@ contract DisputeKitPlurality is AbstractDisputeKit {
dispute.penaltiesForRound.push(0);
dispute.ruling = 0;
- disputeIDtoRounds[_disputeID].push();
+ disputeIDtoRounds[_disputeID].push();
+ }
+
+ function getVotes(uint _disputeID) public view returns(Vote[][] memory) {
+ return disputes[_disputeID].votes;
+ }
+
+ function getVotesLength(uint _disputeID) public view returns(uint, uint) {
+ return (disputes[_disputeID].votes.length, disputes[_disputeID].votes[0].length);
+ }
+
+ function getVoteCounter(uint _disputeID) public view returns(bool) {
+ return disputes[_disputeID].voteCounters[disputes[_disputeID].voteCounters.length - 1].tied;
}
/**
@@ -165,10 +173,7 @@ contract DisputeKitPlurality is AbstractDisputeKit {
for (uint256 i = 0; i < _iterations; i++) {
uint256 treeIndex = draw(uint256(keccak256(abi.encodePacked(randomNumber, _disputeID, i))), k, nodes);
bytes32 id = core.getSortitionSumTreeID(key, treeIndex);
- (
- address drawnAddress,
- /* subcourtID */
- ) = stakePathIDToAccountAndSubcourtID(id);
+ (address drawnAddress, /* subcourtID */) = stakePathIDToAccountAndSubcourtID(id);
// TODO: Save the vote.
// dispute.votes[dispute.votes.length - 1][i].account = drawnAddress;
diff --git a/contracts/src/arbitration/mock/MockKlerosCore.sol b/contracts/src/arbitration/mock/MockKlerosCore.sol
index 14c9b7f1e..2b0521fff 100644
--- a/contracts/src/arbitration/mock/MockKlerosCore.sol
+++ b/contracts/src/arbitration/mock/MockKlerosCore.sol
@@ -2,14 +2,12 @@
pragma solidity ^0.8;
-import {SortitionSumTreeFactory} from "../../data-structures/SortitionSumTreeFactory.sol";
-// import "../DisputeKitPlurality.sol";
+// import {SortitionSumTreeFactory} from "../../data-structures/SortitionSumTreeFactory.sol";
import "../IArbitrable.sol";
import "../AbstractDisputeKit.sol";
contract MockKlerosCore {
- using SortitionSumTreeFactory for SortitionSumTreeFactory.SortitionSumTrees; // Use library functions for sortition sum trees.
- SortitionSumTreeFactory.SortitionSumTrees internal sortitionSumTrees; // The sortition sum trees.
+ SortitionSumTrees internal sortitionSumTrees; // The sortition sum trees.
struct Dispute {
// Note that appeal `0` is equivalent to the first round of the dispute.
@@ -45,6 +43,8 @@ contract MockKlerosCore {
uint[4] timesPerPeriod; // The time allotted to each dispute period in the form `timesPerPeriod[period]`.
}
+ event DisputeCreation(uint indexed _disputeID, IArbitrable indexed _arbitrable);
+
uint public constant MIN_JURORS = 3; // The global default minimum number of jurors in a dispute.
Dispute[] public disputes;
@@ -53,7 +53,10 @@ contract MockKlerosCore {
AbstractDisputeKit disputeKit;
constructor() {
- sortitionSumTrees.createTree(bytes32(0), 3);
+ createTree(sortitionSumTrees, bytes32(0), 3);
+ Court storage court = courts.push();
+ court.minStake = 100;
+ court.feeForJuror = 100;
}
// TODO: only owner
@@ -76,7 +79,7 @@ contract MockKlerosCore {
_choices,
_extraData);
- //emit DisputeCreation(disputeID, IArbitrable(msg.sender));
+ emit DisputeCreation(disputeID, IArbitrable(msg.sender));
}
function getSortitionSumTree(bytes32 _key)
@@ -88,7 +91,7 @@ contract MockKlerosCore {
uint256[] memory nodes
)
{
- SortitionSumTreeFactory.SortitionSumTree storage tree = sortitionSumTrees.sortitionSumTrees[_key];
+ SortitionSumTree storage tree = sortitionSumTrees.sortitionSumTrees[_key];
k = tree.K;
stack = tree.stack;
nodes = tree.nodes;
@@ -120,4 +123,35 @@ contract MockKlerosCore {
minJurors = MIN_JURORS;
}
}
+
+
+ // SORTITION TREE FACTORY
+
+ struct SortitionSumTree {
+ uint256 K; // The maximum number of childs per node.
+ // We use this to keep track of vacant positions in the tree after removing a leaf. This is for keeping the tree as balanced as possible without spending gas on moving nodes around.
+ uint256[] stack;
+ uint256[] nodes;
+ // Two-way mapping of IDs to node indexes. Note that node index 0 is reserved for the root node, and means the ID does not have a node.
+ mapping(bytes32 => uint256) IDsToNodeIndexes;
+ mapping(uint256 => bytes32) nodeIndexesToIDs;
+ }
+
+ struct SortitionSumTrees {
+ mapping(bytes32 => SortitionSumTree) sortitionSumTrees;
+ }
+
+ function createTree(
+ SortitionSumTrees storage self,
+ bytes32 _key,
+ uint256 _K
+ ) private {
+ SortitionSumTree storage tree = self.sortitionSumTrees[_key];
+ require(tree.K == 0, "Tree already exists.");
+ require(_K > 1, "K must be greater than one.");
+ tree.K = _K;
+ // tree.stack.length = 0;
+ // tree.nodes.length = 0;
+ tree.nodes.push(0);
+ }
}
diff --git a/contracts/test/arbitration/index.ts b/contracts/test/arbitration/index.ts
new file mode 100644
index 000000000..52140b2be
--- /dev/null
+++ b/contracts/test/arbitration/index.ts
@@ -0,0 +1,63 @@
+import { expect } from "chai";
+import { ethers } from "hardhat";
+import { BigNumber } from "ethers";
+
+const ONE_ETH = BigNumber.from(10).pow(18);
+const WINNER_STAKE_MULTIPLIER = 3000;
+const LOSER_STAKE_MULTIPLIER = 7000;
+const MULTIPLIER_DENOMINATOR = 10000;
+
+function sleep(ms) {
+ return new Promise((resolve) => setTimeout(resolve, ms));
+}
+
+describe("DisputeKitPlurality", function () {
+ // eslint-disable-next-line no-unused-vars
+ let deployer, claimant, supporter, challenger, innocentBystander;
+ let core, disputeKit, arbitrable;
+
+ before("Deploying", async () => {
+ [deployer, claimant, supporter, challenger, innocentBystander] = await ethers.getSigners();
+ [core, disputeKit, arbitrable] = await deployContracts(deployer);
+
+ // To wait for eth gas reporter to fetch data. Remove this line when the issue is fixed. https://github.com/cgewecke/hardhat-gas-reporter/issues/72
+ // await sleep(9000);
+ });
+
+ it("Should create a dispute", async function () {
+ await expect(disputeKit.connect(deployer).createDispute(0, 0, 0, 0, 0, 0, "0x00"))
+ .to.be.revertedWith("Not allowed: sender is not core");
+
+ await expect(core.connect(deployer).createDispute(2, "0x00", { value: 1000 }))
+ .to.emit(core, "DisputeCreation")
+ .withArgs(0, deployer.address);
+
+ console.log(await disputeKit.disputes(0));
+ console.log(`votes=${await disputeKit.getVotes(0)}`);
+ console.log(`votes=${await disputeKit.getVotesLength(0)}`);
+ console.log(`voteCounter=${await disputeKit.getVoteCounter(0)}`);
+ });
+});
+
+
+async function deployContracts(deployer) {
+ const MockKlerosCoreFactory = await ethers.getContractFactory("MockKlerosCore", deployer);
+ const core = await MockKlerosCoreFactory.deploy();
+ await core.deployed();
+
+ const ConstantNGFactory = await ethers.getContractFactory("ConstantNG", deployer);
+ const rng = await ConstantNGFactory.deploy(42);
+ await rng.deployed();
+
+ const disputeKitFactory = await ethers.getContractFactory("DisputeKitPlurality", deployer);
+ const disputeKit = await disputeKitFactory.deploy(core.address, rng.address);
+ await disputeKit.deployed();
+
+ await core.registerDisputeKit(disputeKit.address);
+
+ const ArbitrableFactory = await ethers.getContractFactory("ArbitrableExample", deployer);
+ const arbitrable = await ArbitrableFactory.deploy(core.address);
+ await arbitrable.deployed();
+
+ return [core, disputeKit, arbitrable];
+}
From 2795912599f73ddef3ddd219a954348628f15920 Mon Sep 17 00:00:00 2001
From: jaybuidl
Date: Tue, 28 Dec 2021 16:26:23 +0000
Subject: [PATCH 06/14] chore: prettify and removal of CHANGELOG.md (cause of
too many conflicts)
---
.husky/pre-commit | 4 +-
CHANGELOG.md | 39 --
.../src/arbitration/AbstractDisputeKit.sol | 2 -
.../src/arbitration/DisputeKitPlurality.sol | 49 ++-
.../src/arbitration/mock/MockKlerosCore.sol | 35 +-
contracts/test/arbitration/index.ts | 10 +-
contracts/test/evidence/index.ts | 413 +++++++++---------
yarn.lock | 14 +-
8 files changed, 260 insertions(+), 306 deletions(-)
delete mode 100644 CHANGELOG.md
diff --git a/.husky/pre-commit b/.husky/pre-commit
index d827c7000..dd535a8e0 100755
--- a/.husky/pre-commit
+++ b/.husky/pre-commit
@@ -2,6 +2,4 @@
. "$(dirname "$0")/_/husky.sh"
yarn lint-staged \
- && yarn depcheck \
- && yarn changelog \
- && git add CHANGELOG.md
+ && yarn depcheck
diff --git a/CHANGELOG.md b/CHANGELOG.md
deleted file mode 100644
index c970eb513..000000000
--- a/CHANGELOG.md
+++ /dev/null
@@ -1,39 +0,0 @@
-## 0.1.0 (2021-12-28)
-
-- test: add evidence contract tests ([590d800](https://github.com/kleros/kleros-v2/commit/590d800))
-- test: added a test for IncrementalNG ([65a996b](https://github.com/kleros/kleros-v2/commit/65a996b))
-- test(DisputeKitPlurality): added basic test, work in progress ([cd4a3df](https://github.com/kleros/kleros-v2/commit/cd4a3df))
-- test(EvidenceModule): add test file ([9f00f98](https://github.com/kleros/kleros-v2/commit/9f00f98))
-- feat: add arbitrum L1 bridge and dependencies ([b412772](https://github.com/kleros/kleros-v2/commit/b412772))
-- feat: add arbitrum L2 bridge ([457b060](https://github.com/kleros/kleros-v2/commit/457b060))
-- feat: dispute kit in progress ([95b8ada](https://github.com/kleros/kleros-v2/commit/95b8ada))
-- feat: modern toolchain setup and simple RNG smart contracts ([17f6a76](https://github.com/kleros/kleros-v2/commit/17f6a76))
-- feat(Arbitration): standard update ([ed930de](https://github.com/kleros/kleros-v2/commit/ed930de))
-- feat(DisputeKitPlurality): dispute creation ([6a2490e](https://github.com/kleros/kleros-v2/commit/6a2490e))
-- feat(DisputeKitPlurality): split the responsibility of drawing jurors between Core and Dispute Kit ([f339e2b](https://github.com/kleros/kleros-v2/commit/f339e2b))
-- refactor: add arbitrator data index getter ([47a1623](https://github.com/kleros/kleros-v2/commit/47a1623))
-- refactor: add evidence contracts ([09a34f3](https://github.com/kleros/kleros-v2/commit/09a34f3))
-- refactor: add interfaces + capped math ([e25b21f](https://github.com/kleros/kleros-v2/commit/e25b21f))
-- refactor: add simple evidence home contract ([1b82f62](https://github.com/kleros/kleros-v2/commit/1b82f62))
-- refactor: fix contract name ([6eb744a](https://github.com/kleros/kleros-v2/commit/6eb744a))
-- refactor: remove foreign evidence interface ([ff8c50c](https://github.com/kleros/kleros-v2/commit/ff8c50c))
-- refactor(bridge): use ArbRetryableTx#getSubmissionPrice ([61bc2f3](https://github.com/kleros/kleros-v2/commit/61bc2f3))
-- refactor(sdk): rename ([3241d10](https://github.com/kleros/kleros-v2/commit/3241d10))
-- chore: .gitignore ([0ed4d74](https://github.com/kleros/kleros-v2/commit/0ed4d74))
-- chore: .gitignore and removal of unnecessary yarn cache as we are using "zero installs" ([a6cfdd0](https://github.com/kleros/kleros-v2/commit/a6cfdd0))
-- chore: added GitHub code scanning ([4a70475](https://github.com/kleros/kleros-v2/commit/4a70475))
-- chore: added the hardhat config for layer 2 networks, added hardhat-deploy and mocha ([a12ea0e](https://github.com/kleros/kleros-v2/commit/a12ea0e))
-- chore: gitignore typechain ([b50f777](https://github.com/kleros/kleros-v2/commit/b50f777))
-- chore(typechain): clean generated files ([775ddd0](https://github.com/kleros/kleros-v2/commit/775ddd0))
-- fix: according to evidence standard + comments ([5c95828](https://github.com/kleros/kleros-v2/commit/5c95828))
-- fix: unused code ([26b5dc3](https://github.com/kleros/kleros-v2/commit/26b5dc3))
-- fix(Arbitrator): memory to calldata ([4770b1f](https://github.com/kleros/kleros-v2/commit/4770b1f))
-- fix(EvidenceModule): typos + castings + imports ([789c022](https://github.com/kleros/kleros-v2/commit/789c022))
-- fix(IArbitrator): appeals removed from the standard ([02c20ce](https://github.com/kleros/kleros-v2/commit/02c20ce))
-- fix(IArbitrator): change name to arbitration cost ([0ba4f29](https://github.com/kleros/kleros-v2/commit/0ba4f29))
-- fix(IArbitrator): interface simplification ([e81fb8b](https://github.com/kleros/kleros-v2/commit/e81fb8b))
-- fix(IArbitrator): replaced appealCost with fundingStatus ([f189dd9](https://github.com/kleros/kleros-v2/commit/f189dd9))
-- docs: initial commit ([23356e7](https://github.com/kleros/kleros-v2/commit/23356e7))
-- docs: license file added ([cb62d2c](https://github.com/kleros/kleros-v2/commit/cb62d2c))
-- docs: readme and spdx headers ([8a5b397](https://github.com/kleros/kleros-v2/commit/8a5b397))
-- docs: updated ([5b9a8f1](https://github.com/kleros/kleros-v2/commit/5b9a8f1))
diff --git a/contracts/src/arbitration/AbstractDisputeKit.sol b/contracts/src/arbitration/AbstractDisputeKit.sol
index a360ff384..577b8003e 100644
--- a/contracts/src/arbitration/AbstractDisputeKit.sol
+++ b/contracts/src/arbitration/AbstractDisputeKit.sol
@@ -5,7 +5,6 @@ pragma solidity ^0.8;
import "./IArbitrator.sol";
abstract contract AbstractDisputeKit {
-
/**
* Note: disputeID is maintained by Kleros Core, not the dispute kit
* Note: the dispute kit does not receive any payment, Kleros Core does
@@ -27,5 +26,4 @@ abstract contract AbstractDisputeKit {
* @param _iterations The number of iterations to run.
*/
function drawJurors(uint256 _disputeID, uint256 _iterations) external virtual;
-
}
diff --git a/contracts/src/arbitration/DisputeKitPlurality.sol b/contracts/src/arbitration/DisputeKitPlurality.sol
index 940118ef0..d0b11a397 100644
--- a/contracts/src/arbitration/DisputeKitPlurality.sol
+++ b/contracts/src/arbitration/DisputeKitPlurality.sol
@@ -34,13 +34,13 @@ contract DisputeKitPlurality is AbstractDisputeKit {
struct Vote {
address account; // The address of the juror.
bytes32 commit; // The commit of the juror. For courts with hidden votes.
- uint choice; // The choice of the juror.
+ uint256 choice; // The choice of the juror.
bool voted; // True if the vote has been cast or revealed, false otherwise.
}
struct VoteCounter {
// 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.
- uint winningChoice;
- mapping(uint => uint) counts; // The sum of votes for each choice in the form `counts[choice]`.
+ uint256 winningChoice;
+ 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.
}
struct Dispute {
@@ -48,16 +48,16 @@ contract DisputeKitPlurality is AbstractDisputeKit {
//IArbitrable arbitrated; // The address of the arbitrable contract.
bytes arbitratorExtraData; // Extra data for the arbitrator.
uint256 choices; // The number of choices the arbitrator can choose from.
- uint256 appealPeriodStart; // Time when the appeal funding becomes possible.
+ uint256 appealPeriodStart; // Time when the appeal funding becomes possible.
Vote[][] votes; // The votes in the form `votes[appeal][voteID]`. On each round, a new list is pushed and packed with as many empty votes as there are draws. We use `dispute.votes.length` to get the number of appeals plus 1 for the first round.
VoteCounter[] voteCounters; // The vote counters in the form `voteCounters[appeal]`.
- uint[] tokensAtStakePerJurorForRound; // The amount of tokens at stake for each juror in the form `tokensAtStakePerJuror[appeal]`.
- uint[] arbitrationFeeForRound; // Fee paid by the arbitrable for the arbitration for each round. Must be equal or higher than arbitration cost.
- uint drawsInCurrentRound; // A counter of draws made in the current round.
- uint commitsInCurrentRound; // A counter of commits made in the current round.
- uint[] votesForRound; // A counter of votes made in each round in the form `votesInEachRound[appeal]`.
- uint[] repartitionsForRound; // A counter of vote reward repartitions made in each round in the form `repartitionsInEachRound[appeal]`.
- uint[] penaltiesForRound; // The amount of tokens collected from penalties in each round in the form `penaltiesInEachRound[appeal]`.
+ uint256[] tokensAtStakePerJurorForRound; // The amount of tokens at stake for each juror in the form `tokensAtStakePerJuror[appeal]`.
+ uint256[] arbitrationFeeForRound; // Fee paid by the arbitrable for the arbitration for each round. Must be equal or higher than arbitration cost.
+ uint256 drawsInCurrentRound; // A counter of draws made in the current round.
+ uint256 commitsInCurrentRound; // A counter of commits made in the current round.
+ uint256[] votesForRound; // A counter of votes made in each round in the form `votesInEachRound[appeal]`.
+ uint256[] repartitionsForRound; // A counter of vote reward repartitions made in each round in the form `repartitionsInEachRound[appeal]`.
+ uint256[] penaltiesForRound; // The amount of tokens collected from penalties in each round in the form `penaltiesInEachRound[appeal]`.
bool ruled; // True if the ruling has been executed, false otherwise.
uint256 ruling; // Ruling given by the arbitrator.
}
@@ -74,7 +74,7 @@ contract DisputeKitPlurality is AbstractDisputeKit {
// * STORAGE * //
// ************************ //
- uint public constant ALPHA_DIVISOR = 1e4; // The number to divide `Court.alpha` by.
+ uint256 public constant ALPHA_DIVISOR = 1e4; // The number to divide `Court.alpha` by.
// TODO: extract the necessary interfaces
MockKlerosCore public immutable core;
@@ -124,10 +124,9 @@ contract DisputeKitPlurality is AbstractDisputeKit {
dispute.choices = _choices;
dispute.appealPeriodStart = 0;
- uint numberOfVotes = _arbitrationFee / _subcourtFeeForJuror;
+ uint256 numberOfVotes = _arbitrationFee / _subcourtFeeForJuror;
Vote[] storage votes = dispute.votes.push(); // TODO: the array dimensions may be reversed, check!
- while (votes.length < numberOfVotes)
- votes.push();
+ while (votes.length < numberOfVotes) votes.push();
dispute.voteCounters.push().tied = true;
dispute.tokensAtStakePerJurorForRound.push((_subcourtMinStake * _subcourtAlpha) / ALPHA_DIVISOR);
@@ -137,18 +136,18 @@ contract DisputeKitPlurality is AbstractDisputeKit {
dispute.penaltiesForRound.push(0);
dispute.ruling = 0;
- disputeIDtoRounds[_disputeID].push();
- }
+ disputeIDtoRounds[_disputeID].push();
+ }
- function getVotes(uint _disputeID) public view returns(Vote[][] memory) {
+ function getVotes(uint256 _disputeID) public view returns (Vote[][] memory) {
return disputes[_disputeID].votes;
}
- function getVotesLength(uint _disputeID) public view returns(uint, uint) {
+ function getVotesLength(uint256 _disputeID) public view returns (uint256, uint256) {
return (disputes[_disputeID].votes.length, disputes[_disputeID].votes[0].length);
}
- function getVoteCounter(uint _disputeID) public view returns(bool) {
+ function getVoteCounter(uint256 _disputeID) public view returns (bool) {
return disputes[_disputeID].voteCounters[disputes[_disputeID].voteCounters.length - 1].tied;
}
@@ -161,8 +160,9 @@ contract DisputeKitPlurality is AbstractDisputeKit {
uint96 subcourtID = core.getDispute(_disputeID).subcourtID;
bytes32 key = bytes32(bytes12(subcourtID)); // due to new conversion restrictions in v0.8
(
- uint256 k,
- /* stack */ ,
+ uint256 k,
+ ,
+ /* stack */
uint256[] memory nodes
) = core.getSortitionSumTree(key);
@@ -173,7 +173,10 @@ contract DisputeKitPlurality is AbstractDisputeKit {
for (uint256 i = 0; i < _iterations; i++) {
uint256 treeIndex = draw(uint256(keccak256(abi.encodePacked(randomNumber, _disputeID, i))), k, nodes);
bytes32 id = core.getSortitionSumTreeID(key, treeIndex);
- (address drawnAddress, /* subcourtID */) = stakePathIDToAccountAndSubcourtID(id);
+ (
+ address drawnAddress, /* subcourtID */
+
+ ) = stakePathIDToAccountAndSubcourtID(id);
// TODO: Save the vote.
// dispute.votes[dispute.votes.length - 1][i].account = drawnAddress;
diff --git a/contracts/src/arbitration/mock/MockKlerosCore.sol b/contracts/src/arbitration/mock/MockKlerosCore.sol
index 2b0521fff..8f277593e 100644
--- a/contracts/src/arbitration/mock/MockKlerosCore.sol
+++ b/contracts/src/arbitration/mock/MockKlerosCore.sol
@@ -33,19 +33,19 @@ contract MockKlerosCore {
struct Court {
uint96 parent; // The parent court.
- uint[] children; // List of child courts.
+ uint256[] children; // List of child courts.
bool hiddenVotes; // Whether to use commit and reveal or not.
- uint minStake; // Minimum tokens needed to stake in the court.
- uint alpha; // Basis point of tokens that are lost when incoherent.
- uint feeForJuror; // Arbitration fee paid per juror.
+ uint256 minStake; // Minimum tokens needed to stake in the court.
+ uint256 alpha; // Basis point of tokens that are lost when incoherent.
+ uint256 feeForJuror; // Arbitration fee paid per juror.
// The appeal after the one that reaches this number of jurors will go to the parent court if any, otherwise, no more appeals are possible.
- uint jurorsForCourtJump;
- uint[4] timesPerPeriod; // The time allotted to each dispute period in the form `timesPerPeriod[period]`.
+ uint256 jurorsForCourtJump;
+ uint256[4] timesPerPeriod; // The time allotted to each dispute period in the form `timesPerPeriod[period]`.
}
- event DisputeCreation(uint indexed _disputeID, IArbitrable indexed _arbitrable);
+ event DisputeCreation(uint256 indexed _disputeID, IArbitrable indexed _arbitrable);
- uint public constant MIN_JURORS = 3; // The global default minimum number of jurors in a dispute.
+ uint256 public constant MIN_JURORS = 3; // The global default minimum number of jurors in a dispute.
Dispute[] public disputes;
Court[] public courts;
@@ -65,19 +65,20 @@ contract MockKlerosCore {
}
function createDispute(uint256 _choices, bytes calldata _extraData) external payable returns (uint256 disputeID) {
- (uint96 subcourtID, uint minJurors) = extraDataToSubcourtIDAndMinJurors(_extraData);
+ (uint96 subcourtID, uint256 minJurors) = extraDataToSubcourtIDAndMinJurors(_extraData);
disputeID = disputes.length;
Court storage court = courts[0];
disputeKit.createDispute(
- disputeID,
+ disputeID,
msg.value,
court.feeForJuror,
court.minStake,
court.alpha,
- _choices,
- _extraData);
+ _choices,
+ _extraData
+ );
emit DisputeCreation(disputeID, IArbitrable(msg.sender));
}
@@ -110,9 +111,14 @@ contract MockKlerosCore {
* @return subcourtID The subcourt ID.
* @return minJurors The minimum number of jurors required.
*/
- function extraDataToSubcourtIDAndMinJurors(bytes memory _extraData) internal view returns (uint96 subcourtID, uint minJurors) {
+ function extraDataToSubcourtIDAndMinJurors(bytes memory _extraData)
+ internal
+ view
+ returns (uint96 subcourtID, uint256 minJurors)
+ {
if (_extraData.length >= 64) {
- assembly { // solium-disable-line security/no-inline-assembly
+ assembly {
+ // solium-disable-line security/no-inline-assembly
subcourtID := mload(add(_extraData, 0x20))
minJurors := mload(add(_extraData, 0x40))
}
@@ -124,7 +130,6 @@ contract MockKlerosCore {
}
}
-
// SORTITION TREE FACTORY
struct SortitionSumTree {
diff --git a/contracts/test/arbitration/index.ts b/contracts/test/arbitration/index.ts
index 52140b2be..927c6ba88 100644
--- a/contracts/test/arbitration/index.ts
+++ b/contracts/test/arbitration/index.ts
@@ -13,7 +13,7 @@ function sleep(ms) {
describe("DisputeKitPlurality", function () {
// eslint-disable-next-line no-unused-vars
- let deployer, claimant, supporter, challenger, innocentBystander;
+ let deployer, claimant, supporter, challenger, innocentBystander;
let core, disputeKit, arbitrable;
before("Deploying", async () => {
@@ -21,12 +21,13 @@ describe("DisputeKitPlurality", function () {
[core, disputeKit, arbitrable] = await deployContracts(deployer);
// To wait for eth gas reporter to fetch data. Remove this line when the issue is fixed. https://github.com/cgewecke/hardhat-gas-reporter/issues/72
- // await sleep(9000);
+ // await sleep(9000);
});
it("Should create a dispute", async function () {
- await expect(disputeKit.connect(deployer).createDispute(0, 0, 0, 0, 0, 0, "0x00"))
- .to.be.revertedWith("Not allowed: sender is not core");
+ await expect(disputeKit.connect(deployer).createDispute(0, 0, 0, 0, 0, 0, "0x00")).to.be.revertedWith(
+ "Not allowed: sender is not core"
+ );
await expect(core.connect(deployer).createDispute(2, "0x00", { value: 1000 }))
.to.emit(core, "DisputeCreation")
@@ -39,7 +40,6 @@ describe("DisputeKitPlurality", function () {
});
});
-
async function deployContracts(deployer) {
const MockKlerosCoreFactory = await ethers.getContractFactory("MockKlerosCore", deployer);
const core = await MockKlerosCoreFactory.deploy();
diff --git a/contracts/test/evidence/index.ts b/contracts/test/evidence/index.ts
index 08a238607..b06660711 100644
--- a/contracts/test/evidence/index.ts
+++ b/contracts/test/evidence/index.ts
@@ -1,62 +1,52 @@
import { use, expect } from "chai";
import { ethers } from "hardhat";
import { BigNumber } from "ethers";
-import { solidity } from 'ethereum-waffle';
-const hre = require('hardhat')
+import { solidity } from "ethereum-waffle";
+const hre = require("hardhat");
-use(solidity)
+use(solidity);
const Party = {
None: 0,
- Submitter: 1,
- Moderator: 2
-}
+ Submitter: 1,
+ Moderator: 2,
+};
function getEmittedEvent(eventName: any, receipt: any) {
- return receipt.events.find(({ event }) => event === eventName)
+ return receipt.events.find(({ event }) => event === eventName);
}
-describe('Home Evidence contract', async () => {
- const arbitrationFee = 1000
- const appealFee = arbitrationFee
- const arbitratorExtraData = '0x85'
- const appealTimeout = 100
- const bondTimeout = 60 * 10
- const totalCostMultiplier = 15000
- const initialDepositMultiplier = 625
- const metaEvidenceUri = 'https://kleros.io'
- const MULTIPLIER_DIVISOR = BigNumber.from(10000)
- const totalCost = BigNumber.from(arbitrationFee).mul(BigNumber.from(totalCostMultiplier)).div(MULTIPLIER_DIVISOR)
- const minRequiredDeposit = totalCost.mul(BigNumber.from(initialDepositMultiplier)).div(MULTIPLIER_DIVISOR)
- const ZERO = BigNumber.from(0)
-
- let deployer
- let user1
- let user2
- let user3
- let user4
- let evidenceID
-
- let arbitrator
- let evidenceModule
-
- beforeEach('Setup contracts', async () => {
- ;[
- deployer,
- user1,
- user2,
- user3,
- user4
- ] = await ethers.getSigners()
-
- const Arbitrator = await ethers.getContractFactory('CentralizedArbitrator')
- arbitrator = await Arbitrator.deploy(
- String(arbitrationFee),
- appealTimeout,
- String(appealFee)
- )
-
- const EvidenceModule = await ethers.getContractFactory('ModeratedEvidenceModule')
+describe("Home Evidence contract", async () => {
+ const arbitrationFee = 1000;
+ const appealFee = arbitrationFee;
+ const arbitratorExtraData = "0x85";
+ const appealTimeout = 100;
+ const bondTimeout = 60 * 10;
+ const totalCostMultiplier = 15000;
+ const initialDepositMultiplier = 625;
+ const metaEvidenceUri = "https://kleros.io";
+ const MULTIPLIER_DIVISOR = BigNumber.from(10000);
+ const totalCost = BigNumber.from(arbitrationFee).mul(BigNumber.from(totalCostMultiplier)).div(MULTIPLIER_DIVISOR);
+ const minRequiredDeposit = totalCost.mul(BigNumber.from(initialDepositMultiplier)).div(MULTIPLIER_DIVISOR);
+ const ZERO = BigNumber.from(0);
+
+ let deployer;
+ let user1;
+ let user2;
+ let user3;
+ let user4;
+ let evidenceID;
+
+ let arbitrator;
+ let evidenceModule;
+
+ beforeEach("Setup contracts", async () => {
+ [deployer, user1, user2, user3, user4] = await ethers.getSigners();
+
+ const Arbitrator = await ethers.getContractFactory("CentralizedArbitrator");
+ arbitrator = await Arbitrator.deploy(String(arbitrationFee), appealTimeout, String(appealFee));
+
+ const EvidenceModule = await ethers.getContractFactory("ModeratedEvidenceModule");
evidenceModule = await EvidenceModule.deploy(
arbitrator.address,
deployer.address, // governor
@@ -65,212 +55,199 @@ describe('Home Evidence contract', async () => {
bondTimeout,
arbitratorExtraData,
metaEvidenceUri
- )
- })
+ );
+ });
describe("Governance", function () {
it("Should change parameters correctly", async function () {
- const newGovernor = await user2.getAddress()
- await evidenceModule.changeGovernor(newGovernor)
- expect(await evidenceModule.governor()).to.equal(newGovernor)
- await evidenceModule.connect(user2).changeGovernor(await deployer.getAddress())
-
- await evidenceModule.changeInitialDepositMultiplier(1)
- expect(await evidenceModule.initialDepositMultiplier()).to.equal(1)
-
- await evidenceModule.changeTotalCostMultiplier(1)
- expect(await evidenceModule.totalCostMultiplier()).to.equal(1)
-
- await evidenceModule.changeBondTimeout(1)
- expect(await evidenceModule.bondTimeout()).to.equal(1)
-
- const newMetaEvidenceUri = 'https://kleros.io/new'
- let tx = await evidenceModule.changeMetaEvidence(newMetaEvidenceUri)
- let receipt = await tx.wait()
- let lastArbitratorIndex = await evidenceModule.getCurrentArbitratorIndex()
- let newArbitratorData = await evidenceModule.arbitratorDataList(lastArbitratorIndex)
- let oldArbitratorData = await evidenceModule.arbitratorDataList(lastArbitratorIndex.sub(BigNumber.from(1)))
-
- expect(
- newArbitratorData.metaEvidenceUpdates
- ).to.equal(oldArbitratorData.metaEvidenceUpdates.add(BigNumber.from(1)))
- expect(
- newArbitratorData.arbitratorExtraData
- ).to.equal(oldArbitratorData.arbitratorExtraData)
- const [newMetaEvidenceUpdates ,newMetaEvidence] = getEmittedEvent('MetaEvidence', receipt).args
- expect(newMetaEvidence).to.equal(newMetaEvidenceUri, 'Wrong MetaEvidence.')
- expect(newMetaEvidenceUpdates).to.equal(newArbitratorData.metaEvidenceUpdates, 'Wrong MetaEvidence ID.')
-
- const newArbitratorExtraData = '0x86'
- await evidenceModule.changeArbitratorExtraData(newArbitratorExtraData)
- newArbitratorData = await evidenceModule.arbitratorDataList(lastArbitratorIndex.add(BigNumber.from(1)))
- expect(
- newArbitratorData.arbitratorExtraData
- ).to.equal(newArbitratorExtraData, 'Wrong extraData')
+ const newGovernor = await user2.getAddress();
+ await evidenceModule.changeGovernor(newGovernor);
+ expect(await evidenceModule.governor()).to.equal(newGovernor);
+ await evidenceModule.connect(user2).changeGovernor(await deployer.getAddress());
+
+ await evidenceModule.changeInitialDepositMultiplier(1);
+ expect(await evidenceModule.initialDepositMultiplier()).to.equal(1);
+
+ await evidenceModule.changeTotalCostMultiplier(1);
+ expect(await evidenceModule.totalCostMultiplier()).to.equal(1);
+
+ await evidenceModule.changeBondTimeout(1);
+ expect(await evidenceModule.bondTimeout()).to.equal(1);
+
+ const newMetaEvidenceUri = "https://kleros.io/new";
+ let tx = await evidenceModule.changeMetaEvidence(newMetaEvidenceUri);
+ let receipt = await tx.wait();
+ let lastArbitratorIndex = await evidenceModule.getCurrentArbitratorIndex();
+ let newArbitratorData = await evidenceModule.arbitratorDataList(lastArbitratorIndex);
+ let oldArbitratorData = await evidenceModule.arbitratorDataList(lastArbitratorIndex.sub(BigNumber.from(1)));
+
+ expect(newArbitratorData.metaEvidenceUpdates).to.equal(
+ oldArbitratorData.metaEvidenceUpdates.add(BigNumber.from(1))
+ );
+ expect(newArbitratorData.arbitratorExtraData).to.equal(oldArbitratorData.arbitratorExtraData);
+ const [newMetaEvidenceUpdates, newMetaEvidence] = getEmittedEvent("MetaEvidence", receipt).args;
+ expect(newMetaEvidence).to.equal(newMetaEvidenceUri, "Wrong MetaEvidence.");
+ expect(newMetaEvidenceUpdates).to.equal(newArbitratorData.metaEvidenceUpdates, "Wrong MetaEvidence ID.");
+
+ const newArbitratorExtraData = "0x86";
+ await evidenceModule.changeArbitratorExtraData(newArbitratorExtraData);
+ newArbitratorData = await evidenceModule.arbitratorDataList(lastArbitratorIndex.add(BigNumber.from(1)));
+ expect(newArbitratorData.arbitratorExtraData).to.equal(newArbitratorExtraData, "Wrong extraData");
});
it("Should revert if the caller is not the governor", async function () {
- await expect(evidenceModule.
- connect(user2).changeGovernor(await user2.getAddress())
- ).to.be.revertedWith('The caller must be the governor')
+ await expect(evidenceModule.connect(user2).changeGovernor(await user2.getAddress())).to.be.revertedWith(
+ "The caller must be the governor"
+ );
- await expect(evidenceModule.
- connect(user2).changeInitialDepositMultiplier(0)
- ).to.be.revertedWith('The caller must be the governor')
+ await expect(evidenceModule.connect(user2).changeInitialDepositMultiplier(0)).to.be.revertedWith(
+ "The caller must be the governor"
+ );
- await expect(evidenceModule.
- connect(user2).changeTotalCostMultiplier(0)
- ).to.be.revertedWith('The caller must be the governor')
+ await expect(evidenceModule.connect(user2).changeTotalCostMultiplier(0)).to.be.revertedWith(
+ "The caller must be the governor"
+ );
- await expect(evidenceModule.
- connect(user2).changeBondTimeout(0)
- ).to.be.revertedWith('The caller must be the governor')
+ await expect(evidenceModule.connect(user2).changeBondTimeout(0)).to.be.revertedWith(
+ "The caller must be the governor"
+ );
- await expect(evidenceModule.
- connect(user2).changeMetaEvidence(metaEvidenceUri)
- ).to.be.revertedWith('The caller must be the governor')
+ await expect(evidenceModule.connect(user2).changeMetaEvidence(metaEvidenceUri)).to.be.revertedWith(
+ "The caller must be the governor"
+ );
- await expect(evidenceModule.
- connect(user2).changeArbitratorExtraData(arbitratorExtraData)
- ).to.be.revertedWith('The caller must be the governor')
+ await expect(evidenceModule.connect(user2).changeArbitratorExtraData(arbitratorExtraData)).to.be.revertedWith(
+ "The caller must be the governor"
+ );
});
});
-
- describe('Evidence Submission', () => {
- it('Should submit evidence correctly.', async () => {
- const newEvidence = 'Irrefutable evidence'
+
+ describe("Evidence Submission", () => {
+ it("Should submit evidence correctly.", async () => {
+ const newEvidence = "Irrefutable evidence";
const tx = await evidenceModule.connect(user1).submitEvidence(1234, newEvidence, {
- value: minRequiredDeposit
- }) // id: 0
- const receipt = await tx.wait()
+ value: minRequiredDeposit,
+ }); // id: 0
+ const receipt = await tx.wait();
const evidenceID = ethers.utils.solidityKeccak256(["uint", "string"], [1234, newEvidence]);
- const [
- arbitratorAddress,
- evidenceGroupID,
- submitter,
- evidenceStr
- ] = getEmittedEvent('Evidence', receipt).args
- expect(arbitratorAddress).to.equal(arbitrator.address, 'Wrong arbitrator.')
- expect(evidenceGroupID).to.equal(1234, 'Wrong evidence group ID.')
- expect(submitter).to.equal(user1.address, 'Wrong submitter.')
- expect(evidenceStr).to.equal(newEvidence, 'Wrong evidence message.')
-
- let contributions = await evidenceModule.getContributions(evidenceID, 0, user1.address)
- expect(contributions).to.deep.equal([ZERO,minRequiredDeposit,ZERO], 'Wrong contributions.')
- })
-
- it('Should not allowed the same evidence twice for the same evidence group id.', async () => {
- const newEvidence = 'Irrefutable evidence'
+ const [arbitratorAddress, evidenceGroupID, submitter, evidenceStr] = getEmittedEvent("Evidence", receipt).args;
+ expect(arbitratorAddress).to.equal(arbitrator.address, "Wrong arbitrator.");
+ expect(evidenceGroupID).to.equal(1234, "Wrong evidence group ID.");
+ expect(submitter).to.equal(user1.address, "Wrong submitter.");
+ expect(evidenceStr).to.equal(newEvidence, "Wrong evidence message.");
+
+ let contributions = await evidenceModule.getContributions(evidenceID, 0, user1.address);
+ expect(contributions).to.deep.equal([ZERO, minRequiredDeposit, ZERO], "Wrong contributions.");
+ });
+
+ it("Should not allowed the same evidence twice for the same evidence group id.", async () => {
+ const newEvidence = "Irrefutable evidence";
await evidenceModule.submitEvidence(1234, newEvidence, {
- value: minRequiredDeposit
- })
- await expect(evidenceModule.
- submitEvidence(1234, newEvidence, {
- value: minRequiredDeposit
- })).to.be.revertedWith('Evidence already submitted.')
- })
-
- it('Should revert if deposit is too low.', async () => {
- const newEvidence = 'Irrefutable evidence'
- await expect(evidenceModule.
- submitEvidence(1234, newEvidence, {
- value: minRequiredDeposit.sub(BigNumber.from(1))
- })).to.be.revertedWith('Insufficient funding.')
- })
- })
-
- describe('Moderation', () => {
- beforeEach('Initialize posts and comments', async () => {
- const newEvidence = 'Irrefutable evidence'
+ value: minRequiredDeposit,
+ });
+ await expect(
+ evidenceModule.submitEvidence(1234, newEvidence, {
+ value: minRequiredDeposit,
+ })
+ ).to.be.revertedWith("Evidence already submitted.");
+ });
+
+ it("Should revert if deposit is too low.", async () => {
+ const newEvidence = "Irrefutable evidence";
+ await expect(
+ evidenceModule.submitEvidence(1234, newEvidence, {
+ value: minRequiredDeposit.sub(BigNumber.from(1)),
+ })
+ ).to.be.revertedWith("Insufficient funding.");
+ });
+ });
+
+ describe("Moderation", () => {
+ beforeEach("Initialize posts and comments", async () => {
+ const newEvidence = "Irrefutable evidence";
await evidenceModule.connect(user1).submitEvidence(1234, newEvidence, {
- value: minRequiredDeposit
- })
+ value: minRequiredDeposit,
+ });
evidenceID = ethers.utils.solidityKeccak256(["uint", "string"], [1234, newEvidence]);
- })
+ });
- it('Should not allow moderation after bond timeout passed.', async () => {
- await expect(evidenceModule.
- resolveModerationMarket(evidenceID)
- ).to.be.revertedWith('Moderation still ongoing.')
+ it("Should not allow moderation after bond timeout passed.", async () => {
+ await expect(evidenceModule.resolveModerationMarket(evidenceID)).to.be.revertedWith("Moderation still ongoing.");
- await hre.ethers.provider.send('evm_increaseTime', [60 * 10]);
+ await hre.ethers.provider.send("evm_increaseTime", [60 * 10]);
// Moderate
- await expect(evidenceModule.
- moderate(evidenceID, Party.Moderator, {
- value: totalCost
- })).to.be.revertedWith('Moderation market is closed.')
+ await expect(
+ evidenceModule.moderate(evidenceID, Party.Moderator, {
+ value: totalCost,
+ })
+ ).to.be.revertedWith("Moderation market is closed.");
+
+ await evidenceModule.resolveModerationMarket(evidenceID);
- await evidenceModule.resolveModerationMarket(evidenceID)
-
// After market has been closed, moderation can re-open.
await evidenceModule.moderate(evidenceID, Party.Submitter, {
- value: totalCost
- })
- })
+ value: totalCost,
+ });
+ });
- it('Should create dispute after moderation escalation is complete.', async () => {
+ it("Should create dispute after moderation escalation is complete.", async () => {
await evidenceModule.connect(user2).moderate(evidenceID, Party.Moderator, {
- value: minRequiredDeposit.mul(2)
- })
+ value: minRequiredDeposit.mul(2),
+ });
- let moderationInfo = await evidenceModule.getModerationInfo(evidenceID, 0)
- let paidFees = moderationInfo.paidFees
- let depositRequired = paidFees[Party.Moderator].mul(2).
- sub(paidFees[Party.Submitter])
+ let moderationInfo = await evidenceModule.getModerationInfo(evidenceID, 0);
+ let paidFees = moderationInfo.paidFees;
+ let depositRequired = paidFees[Party.Moderator].mul(2).sub(paidFees[Party.Submitter]);
await evidenceModule.connect(user4).moderate(evidenceID, Party.Submitter, {
- value: depositRequired
- })
+ value: depositRequired,
+ });
- moderationInfo = await evidenceModule.getModerationInfo(evidenceID, 0)
- paidFees = moderationInfo.paidFees
- depositRequired = paidFees[Party.Submitter].mul(2).
- sub(paidFees[Party.Moderator])
+ moderationInfo = await evidenceModule.getModerationInfo(evidenceID, 0);
+ paidFees = moderationInfo.paidFees;
+ depositRequired = paidFees[Party.Submitter].mul(2).sub(paidFees[Party.Moderator]);
await evidenceModule.connect(user2).moderate(evidenceID, Party.Moderator, {
- value: depositRequired
- })
+ value: depositRequired,
+ });
- moderationInfo = await evidenceModule.getModerationInfo(evidenceID, 0)
- paidFees = moderationInfo.paidFees
- depositRequired = paidFees[Party.Moderator].mul(2).
- sub(paidFees[Party.Submitter])
+ moderationInfo = await evidenceModule.getModerationInfo(evidenceID, 0);
+ paidFees = moderationInfo.paidFees;
+ depositRequired = paidFees[Party.Moderator].mul(2).sub(paidFees[Party.Submitter]);
await evidenceModule.connect(user4).moderate(evidenceID, Party.Submitter, {
- value: depositRequired
- })
+ value: depositRequired,
+ });
- moderationInfo = await evidenceModule.getModerationInfo(evidenceID, 0)
- paidFees = moderationInfo.paidFees
- depositRequired = paidFees[Party.Submitter].mul(2).
- sub(paidFees[Party.Moderator])
+ moderationInfo = await evidenceModule.getModerationInfo(evidenceID, 0);
+ paidFees = moderationInfo.paidFees;
+ depositRequired = paidFees[Party.Submitter].mul(2).sub(paidFees[Party.Moderator]);
await evidenceModule.connect(user2).moderate(evidenceID, Party.Moderator, {
- value: depositRequired
- })
+ value: depositRequired,
+ });
- moderationInfo = await evidenceModule.getModerationInfo(evidenceID, 0)
- paidFees = moderationInfo.paidFees
- depositRequired = paidFees[Party.Moderator].mul(2).
- sub(paidFees[Party.Submitter])
+ moderationInfo = await evidenceModule.getModerationInfo(evidenceID, 0);
+ paidFees = moderationInfo.paidFees;
+ depositRequired = paidFees[Party.Moderator].mul(2).sub(paidFees[Party.Submitter]);
let tx = await evidenceModule.connect(user4).moderate(evidenceID, Party.Submitter, {
- value: depositRequired // Less is actually needed. Overpaid fees are reimbursed
- })
- let receipt = await tx.wait()
-
- let [_arbitrator, disputeID, metaEvidenceID, _evidenceID] = getEmittedEvent('Dispute', receipt).args
- expect(_arbitrator).to.equal(arbitrator.address, 'Wrong arbitrator.')
- expect(disputeID).to.equal(0, 'Wrong dispute ID.')
- expect(metaEvidenceID).to.equal(0, 'Wrong meta-evidence ID.')
- expect(_evidenceID).to.equal(evidenceID, 'Wrong evidence ID.')
-
- await expect(evidenceModule.
- connect(user2).moderate(evidenceID, Party.Moderator, {
- value: totalCost
+ value: depositRequired, // Less is actually needed. Overpaid fees are reimbursed
+ });
+ let receipt = await tx.wait();
+
+ let [_arbitrator, disputeID, metaEvidenceID, _evidenceID] = getEmittedEvent("Dispute", receipt).args;
+ expect(_arbitrator).to.equal(arbitrator.address, "Wrong arbitrator.");
+ expect(disputeID).to.equal(0, "Wrong dispute ID.");
+ expect(metaEvidenceID).to.equal(0, "Wrong meta-evidence ID.");
+ expect(_evidenceID).to.equal(evidenceID, "Wrong evidence ID.");
+
+ await expect(
+ evidenceModule.connect(user2).moderate(evidenceID, Party.Moderator, {
+ value: totalCost,
})
- ).to.be.revertedWith('Evidence already disputed.')
-
- await expect(evidenceModule.
- connect(user2).resolveModerationMarket(evidenceID)
- ).to.be.revertedWith('Evidence already disputed.')
- })
- })
-})
+ ).to.be.revertedWith("Evidence already disputed.");
+
+ await expect(evidenceModule.connect(user2).resolveModerationMarket(evidenceID)).to.be.revertedWith(
+ "Evidence already disputed."
+ );
+ });
+ });
+});
diff --git a/yarn.lock b/yarn.lock
index 4a54991a6..a3d572d97 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1012,6 +1012,7 @@ __metadata:
hardhat-deploy: ^0.9.6
hardhat-deploy-ethers: ^0.3.0-beta.11
hardhat-gas-reporter: ^1.0.4
+ hardhat-watcher: ^2.1.1
mocha: ^9.1.3
solhint: ^3.3.6
solidity-coverage: ^0.7.17
@@ -3814,7 +3815,7 @@ __metadata:
languageName: node
linkType: hard
-"chokidar@npm:3.5.2, chokidar@npm:>=3.0.0 <4.0.0, chokidar@npm:^3.4.0, chokidar@npm:^3.5.2":
+"chokidar@npm:3.5.2, chokidar@npm:>=3.0.0 <4.0.0, chokidar@npm:^3.4.0, chokidar@npm:^3.4.3, chokidar@npm:^3.5.2":
version: 3.5.2
resolution: "chokidar@npm:3.5.2"
dependencies:
@@ -7610,6 +7611,17 @@ __metadata:
languageName: node
linkType: hard
+"hardhat-watcher@npm:^2.1.1":
+ version: 2.1.1
+ resolution: "hardhat-watcher@npm:2.1.1"
+ dependencies:
+ chokidar: ^3.4.3
+ peerDependencies:
+ hardhat: ^2.0.0
+ checksum: 1bd2e5b136c189260b9c38995e264eb76ee4b510abae79fe9193ced4817d01c5568eb1944bb9b83628448d927c689c3aa9aed6205ff7e6ad17aa6f14b77a2031
+ languageName: node
+ linkType: hard
+
"hardhat@npm:^2.6.8":
version: 2.6.8
resolution: "hardhat@npm:2.6.8"
From 67f4e9aa55492f266b24e7c5e345a40e5b5c925d Mon Sep 17 00:00:00 2001
From: jaybuidl
Date: Wed, 29 Dec 2021 19:03:36 +0000
Subject: [PATCH 07/14] chore: added hardhat-docgen
---
contracts/hardhat.config.ts | 6 ++++++
contracts/package.json | 4 +++-
2 files changed, 9 insertions(+), 1 deletion(-)
diff --git a/contracts/hardhat.config.ts b/contracts/hardhat.config.ts
index c781cd9a1..14cf7cbf6 100644
--- a/contracts/hardhat.config.ts
+++ b/contracts/hardhat.config.ts
@@ -9,6 +9,7 @@ import "solidity-coverage";
import "hardhat-deploy";
import "hardhat-deploy-ethers";
import "hardhat-watcher";
+import "hardhat-docgen";
dotenv.config();
@@ -128,6 +129,11 @@ const config: HardhatUserConfig = {
files: ["./test/**/*", "./src/**/*"],
},
},
+ docgen: {
+ path: './docs',
+ clear: true,
+ runOnCompile: false,
+ },
};
export default config;
diff --git a/contracts/package.json b/contracts/package.json
index 314423d60..cd48a9cb8 100644
--- a/contracts/package.json
+++ b/contracts/package.json
@@ -15,7 +15,8 @@
"clean": "hardhat clean",
"deploy": "hardhat deploy",
"test": "hardhat test",
- "watch": "hardhat watch"
+ "watch": "hardhat watch",
+ "docgen": "hardhat docgen"
},
"devDependencies": {
"@nomiclabs/hardhat-ethers": "^2.0.2",
@@ -37,6 +38,7 @@
"hardhat": "^2.6.8",
"hardhat-deploy": "^0.9.6",
"hardhat-deploy-ethers": "^0.3.0-beta.11",
+ "hardhat-docgen": "^1.2.1",
"hardhat-gas-reporter": "^1.0.4",
"hardhat-watcher": "^2.1.1",
"json-schema": "^0.4.0",
From e2fd182b2519ec8d6d87bbda03bfd0c4d8fd5190 Mon Sep 17 00:00:00 2001
From: jaybuidl
Date: Wed, 29 Dec 2021 19:15:51 +0000
Subject: [PATCH 08/14] feat(DisputeKit): changeCore() for the governor
Rewrite of the existing automated test.
Added _extraData parameter to createDispute() for possible use in future dispute kits.
Some cosmetic changes.
---
.../src/arbitration/DisputeKitPlurality.sol | 244 ----
contracts/src/arbitration/KlerosCore.sol | 8 +-
.../arbitration/dispute-kits/DisputeKit.sol | 211 +++-
.../src/arbitration/mock/MockKlerosCore.sol | 162 ---
contracts/test/arbitration/index.ts | 57 +-
yarn.lock | 1051 ++++++++++++++++-
6 files changed, 1215 insertions(+), 518 deletions(-)
delete mode 100644 contracts/src/arbitration/DisputeKitPlurality.sol
delete mode 100644 contracts/src/arbitration/mock/MockKlerosCore.sol
diff --git a/contracts/src/arbitration/DisputeKitPlurality.sol b/contracts/src/arbitration/DisputeKitPlurality.sol
deleted file mode 100644
index d0b11a397..000000000
--- a/contracts/src/arbitration/DisputeKitPlurality.sol
+++ /dev/null
@@ -1,244 +0,0 @@
-// SPDX-License-Identifier: MIT
-
-pragma solidity ^0.8;
-
-import "./IArbitrator.sol";
-import "../rng/RNG.sol";
-import "./mock/MockKlerosCore.sol";
-
-contract DisputeKitPlurality is AbstractDisputeKit {
- // Core --> IN
- // createDispute
-
- // OUT -> Core
- // report drawn jurors
- // report ruling
-
- // Jurors -> IN
- // vote
-
- // Anyone -> IN
- // requestAppeal
-
- // INTERNAL
- // draw jurors <-> RNG
- // receive evidence
- // aggregate votes
- // incentive (who earns how much PNK and ETH)
- // appeal crowdfunding
- // redistribution when ruling is final
-
- // ************************ //
- // * STRUCTS * //
- // ************************ //
- 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 or revealed, false otherwise.
- }
- struct VoteCounter {
- // 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.
- uint256 winningChoice;
- 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.
- }
- struct Dispute {
- //NOTE: arbitrated needed?? But it needs the chainId too, not just an address.
- //IArbitrable arbitrated; // The address of the arbitrable contract.
- bytes arbitratorExtraData; // Extra data for the arbitrator.
- uint256 choices; // The number of choices the arbitrator can choose from.
- uint256 appealPeriodStart; // Time when the appeal funding becomes possible.
- Vote[][] votes; // The votes in the form `votes[appeal][voteID]`. On each round, a new list is pushed and packed with as many empty votes as there are draws. We use `dispute.votes.length` to get the number of appeals plus 1 for the first round.
- VoteCounter[] voteCounters; // The vote counters in the form `voteCounters[appeal]`.
- uint256[] tokensAtStakePerJurorForRound; // The amount of tokens at stake for each juror in the form `tokensAtStakePerJuror[appeal]`.
- uint256[] arbitrationFeeForRound; // Fee paid by the arbitrable for the arbitration for each round. Must be equal or higher than arbitration cost.
- uint256 drawsInCurrentRound; // A counter of draws made in the current round.
- uint256 commitsInCurrentRound; // A counter of commits made in the current round.
- uint256[] votesForRound; // A counter of votes made in each round in the form `votesInEachRound[appeal]`.
- uint256[] repartitionsForRound; // A counter of vote reward repartitions made in each round in the form `repartitionsInEachRound[appeal]`.
- uint256[] penaltiesForRound; // The amount of tokens collected from penalties in each round in the form `penaltiesInEachRound[appeal]`.
- bool ruled; // True if the ruling has been executed, false otherwise.
- uint256 ruling; // Ruling given by the arbitrator.
- }
-
- struct Round {
- mapping(uint256 => uint256) paidFees; // Tracks the fees paid for each choice in this round.
- mapping(uint256 => bool) hasPaid; // True if this choice was fully funded, false otherwise.
- mapping(address => mapping(uint256 => 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.
- }
-
- // ************************ //
- // * STORAGE * //
- // ************************ //
-
- uint256 public constant ALPHA_DIVISOR = 1e4; // The number to divide `Court.alpha` by.
-
- // TODO: extract the necessary interfaces
- MockKlerosCore public immutable core;
-
- RNG public immutable rng;
-
- Dispute[] public disputes; // Stores the dispute info. disputes[disputeID].
- mapping(uint256 => Round[]) public disputeIDtoRounds; // Maps dispute IDs to Round array that contains the info about crowdfunding.
-
- constructor(MockKlerosCore _core, RNG _rng) {
- core = _core;
- rng = _rng;
- }
-
- // ************************ //
- // * MODIFIERS * //
- // ************************ //
-
- /**
- * Note: disputeID is maintained by KlerosCore, not the dispute kit
- * Note: the dispute kit does not receive nor validate any payment, KlerosCore does
- * Note: Permissioned
- */
- function createDispute(
- uint256 _disputeID,
- uint256 _arbitrationFee,
- uint256 _subcourtFeeForJuror,
- uint256 _subcourtMinStake,
- uint256 _subcourtAlpha,
- uint256 _choices,
- bytes calldata _extraData
- ) external override {
- require(msg.sender == address(core), "Not allowed: sender is not core");
-
- // -- dispute specific --
- //
-
- // -- subcourt specific --
- // uint minStake: min tokens required to stake on this subcourt
- // bool hiddenVotes:
- // uint alpha: bps of tokens lost when incoherent
- // uint feeForJuror: paid per juror
- // uint jurorsForCourtJump: evaluated by the appeal logic
-
- Dispute storage dispute = disputes.push();
- dispute.arbitratorExtraData = _extraData;
- dispute.choices = _choices;
- dispute.appealPeriodStart = 0;
-
- uint256 numberOfVotes = _arbitrationFee / _subcourtFeeForJuror;
- Vote[] storage votes = dispute.votes.push(); // TODO: the array dimensions may be reversed, check!
- while (votes.length < numberOfVotes) votes.push();
-
- dispute.voteCounters.push().tied = true;
- dispute.tokensAtStakePerJurorForRound.push((_subcourtMinStake * _subcourtAlpha) / ALPHA_DIVISOR);
- dispute.arbitrationFeeForRound.push(_arbitrationFee);
- dispute.votesForRound.push(0);
- dispute.repartitionsForRound.push(0);
- dispute.penaltiesForRound.push(0);
- dispute.ruling = 0;
-
- disputeIDtoRounds[_disputeID].push();
- }
-
- function getVotes(uint256 _disputeID) public view returns (Vote[][] memory) {
- return disputes[_disputeID].votes;
- }
-
- function getVotesLength(uint256 _disputeID) public view returns (uint256, uint256) {
- return (disputes[_disputeID].votes.length, disputes[_disputeID].votes[0].length);
- }
-
- function getVoteCounter(uint256 _disputeID) public view returns (bool) {
- return disputes[_disputeID].voteCounters[disputes[_disputeID].voteCounters.length - 1].tied;
- }
-
- /**
- * @dev Draws jurors for a dispute. Can be called in parts.
- * @param _disputeID The ID of the dispute.
- * @param _iterations The number of iterations to run.
- */
- function drawJurors(uint256 _disputeID, uint256 _iterations) external override {
- uint96 subcourtID = core.getDispute(_disputeID).subcourtID;
- bytes32 key = bytes32(bytes12(subcourtID)); // due to new conversion restrictions in v0.8
- (
- uint256 k,
- ,
- /* stack */
- uint256[] memory nodes
- ) = core.getSortitionSumTree(key);
-
- // TODO: run this only when starting the drawing period
- uint256 randomNumber = rng.getUncorrelatedRN(block.number);
-
- // TODO: batching with boundary checks
- for (uint256 i = 0; i < _iterations; i++) {
- uint256 treeIndex = draw(uint256(keccak256(abi.encodePacked(randomNumber, _disputeID, i))), k, nodes);
- bytes32 id = core.getSortitionSumTreeID(key, treeIndex);
- (
- address drawnAddress, /* subcourtID */
-
- ) = stakePathIDToAccountAndSubcourtID(id);
-
- // TODO: Save the vote.
- // dispute.votes[dispute.votes.length - 1][i].account = drawnAddress;
- // jurors[drawnAddress].lockedTokens += dispute.tokensAtStakePerJuror[dispute.tokensAtStakePerJuror.length - 1];
- // emit Draw(drawnAddress, _disputeID, dispute.votes.length - 1, i);
-
- // TODO: Stop if dispute is fully drawn.
- // if (i == dispute.votes[dispute.votes.length - 1].length - 1) break;
- }
- }
-
- // ************************ //
- // * VIEWS * //
- // ************************ //
-
- function draw(
- uint256 _drawnNumber,
- uint256 _k,
- uint256[] memory _nodes
- ) private pure returns (uint256 treeIndex) {
- uint256 currentDrawnNumber = _drawnNumber % _nodes[0];
- while ((_k * treeIndex) + 1 < _nodes.length) {
- // While it still has children.
- for (uint256 i = 1; i <= _k; i++) {
- // Loop over children.
- uint256 nodeIndex = (_k * treeIndex) + i;
- uint256 nodeValue = _nodes[nodeIndex];
-
- if (currentDrawnNumber >= nodeValue)
- currentDrawnNumber -= nodeValue; // Go to the next child.
- else {
- // Pick this child.
- treeIndex = nodeIndex;
- break;
- }
- }
- }
- }
-
- /**
- * @dev Unpacks a stake path ID into an account and a subcourt ID.
- * @param _stakePathID The stake path ID to unpack.
- * @return account The account.
- * @return subcourtID The subcourt ID.
- */
- function stakePathIDToAccountAndSubcourtID(bytes32 _stakePathID)
- internal
- pure
- returns (address account, uint96 subcourtID)
- {
- assembly {
- // solium-disable-line security/no-inline-assembly
- let ptr := mload(0x40)
- for {
- let i := 0x00
- } lt(i, 0x14) {
- i := add(i, 0x01)
- } {
- mstore8(add(add(ptr, 0x0c), i), byte(i, _stakePathID))
- }
- account := mload(ptr)
- subcourtID := _stakePathID
- }
- }
-}
diff --git a/contracts/src/arbitration/KlerosCore.sol b/contracts/src/arbitration/KlerosCore.sol
index a52c896e3..3cc1d86d4 100644
--- a/contracts/src/arbitration/KlerosCore.sol
+++ b/contracts/src/arbitration/KlerosCore.sol
@@ -10,10 +10,10 @@
pragma solidity ^0.8;
-import "./IArbitrator.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
+import "./IArbitrator.sol";
import "./dispute-kits/DisputeKit.sol";
-import "../data-structures/SortitionSumTreeFactory.sol";
+import {SortitionSumTreeFactory} from "../data-structures/SortitionSumTreeFactory.sol";
/**
* @title KlerosCore
@@ -109,7 +109,7 @@ contract KlerosCore is IArbitrator {
/* Modifiers */
modifier onlyByGovernor() {
- require(governor == msg.sender);
+ require(governor == msg.sender, "Access not allowed: Governor only.");
_;
}
@@ -382,7 +382,7 @@ contract KlerosCore is IArbitrator {
dispute.repartitionsInEachRound.push(0);
dispute.penaltiesInEachRound.push(0);
- disputeKit.createDispute(disputeID, _numberOfChoices);
+ disputeKit.createDispute(disputeID, _numberOfChoices, _extraData);
emit DisputeCreation(disputeID, IArbitrable(msg.sender));
}
diff --git a/contracts/src/arbitration/dispute-kits/DisputeKit.sol b/contracts/src/arbitration/dispute-kits/DisputeKit.sol
index 5b3a62b9e..cfbe887ae 100644
--- a/contracts/src/arbitration/dispute-kits/DisputeKit.sol
+++ b/contracts/src/arbitration/dispute-kits/DisputeKit.sol
@@ -1,17 +1,22 @@
// SPDX-License-Identifier: MIT
+/**
+ * @authors: [@unknownunknown1, @jaybuidl]
+ * @reviewers: []
+ * @auditors: []
+ * @bounties: []
+ * @deployments: []
+ */
+
pragma solidity ^0.8;
-import "../IArbitrator.sol";
import "../KlerosCore.sol";
-
-//import "./ChainlinkRNG.sol";
+import "../../rng/RNG.sol";
contract DisputeKit {
- 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 MULTIPLIER_DIVISOR = 10000; // The divisor parameter for multipliers.
+ // ************************************* //
+ // * Structs * //
+ // ************************************* //
struct Dispute {
Round[] rounds; // Rounds of the dispute. 0 is the default round, and [1, ..n] are the appeal rounds.
@@ -39,11 +44,24 @@ contract DisputeKit {
bool voted; // True if the vote has been cast.
}
- KlerosCore public immutable klerosCore;
+ // ************************************* //
+ // * 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
+ RNG public rng; // The random number generator
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.
- uint256 public constant RN = 5; // Mock random number for the drawing process, to temporarily replace the RNG module.
+ // ************************************* //
+ // * Events * //
+ // ************************************* //
event Contribution(
uint256 indexed _disputeID,
@@ -63,16 +81,82 @@ contract DisputeKit {
event ChoiceFunded(uint256 indexed _disputeID, uint256 indexed _round, uint256 indexed _choice);
- constructor(KlerosCore _klerosCore) public {
- klerosCore = _klerosCore;
+ // ************************************* //
+ // * Function Modifiers * //
+ // ************************************* //
+
+ modifier onlyByCore() {
+ require(address(core) == msg.sender, "Access not allowed: KlerosCore only.");
+ _;
+ }
+
+ modifier onlyByGovernor() {
+ require(governor == msg.sender, "Access not allowed: Governor only.");
+ _;
}
+ /** @dev Constructor.
+ * @param _governor The governor's address.
+ * @param _core The KlerosCore arbitrator.
+ * @param _rng The random number generator.
+ */
+ constructor(
+ address _governor,
+ KlerosCore _core,
+ RNG _rng
+ ) {
+ governor = _governor;
+ core = _core;
+ rng = _rng;
+ }
+
+ // ************************ //
+ // * Governance * //
+ // ************************ //
+
+ /** @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 payable _core) external onlyByGovernor {
+ core = KlerosCore(_core);
+ }
+
+ // ************************************* //
+ // * 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 _disputeID The ID of the dispute in Kleros Core.
- * @param _numberOfChoices Number of choices of the disute
+ * @param _numberOfChoices Number of choices of the dispute
+ * @param _extraData Additional info about the dispute, for possible use in future dispute kits.
*/
- function createDispute(uint256 _disputeID, uint256 _numberOfChoices) external {
- require(msg.sender == address(klerosCore), "Can only be called by Kleros Core.");
+ function createDispute(
+ uint256 _disputeID,
+ uint256 _numberOfChoices,
+ bytes calldata _extraData
+ ) external onlyByCore {
uint256 localDisputeID = disputes.length;
Dispute storage dispute = disputes.push();
dispute.numberOfChoices = _numberOfChoices;
@@ -84,15 +168,15 @@ contract DisputeKit {
}
/** @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 _disputeID The ID of the dispute in Kleros Core.
* @return drawnAddress The drawn address.
*/
- function draw(uint256 _disputeID) external returns (address drawnAddress) {
- require(msg.sender == address(klerosCore), "Can only be called by Kleros Core.");
- bytes32 key = bytes32(klerosCore.getSubcourtID(_disputeID)); // Get the ID of the tree.
+ function draw(uint256 _disputeID) external onlyByCore returns (address drawnAddress) {
+ bytes32 key = bytes32(core.getSubcourtID(_disputeID)); // Get the ID of the tree.
uint256 drawnNumber = getRandomNumber();
- (uint256 K, , uint256[] memory nodes) = klerosCore.getSortitionSumTree(key);
+ (uint256 K, , uint256[] memory nodes) = core.getSortitionSumTree(key);
uint256 treeIndex = 0;
uint256 currentDrawnNumber = drawnNumber % nodes[0];
@@ -117,19 +201,12 @@ contract DisputeKit {
}
}
- bytes32 ID = klerosCore.getSortitionSumTreeID(key, treeIndex);
+ bytes32 ID = core.getSortitionSumTreeID(key, treeIndex);
drawnAddress = stakePathIDToAccount(ID);
round.votes.push(Vote({account: drawnAddress, commit: bytes32(0), choice: 0, voted: false}));
}
- /** @dev Mock RNG function.
- */
- function getRandomNumber() public view returns (uint256) {
- // return RNG.getRN(rngRequestId);
- return RN;
- }
-
/** @dev Sets the caller's commit for the specified votes.
* `O(n)` where
* `n` is the number of votes.
@@ -143,7 +220,7 @@ contract DisputeKit {
bytes32 _commit
) external {
require(
- klerosCore.getCurrentPeriod(_disputeID) == KlerosCore.Period.commit,
+ core.getCurrentPeriod(_disputeID) == KlerosCore.Period.commit,
"The dispute should be in Commit period."
);
require(_commit != bytes32(0), "Empty commit.");
@@ -157,7 +234,7 @@ contract DisputeKit {
}
round.totalCommitted += _voteIDs.length;
- if (round.totalCommitted == round.votes.length) klerosCore.passPeriod(_disputeID);
+ if (round.totalCommitted == round.votes.length) core.passPeriod(_disputeID);
}
/** @dev Sets the caller's choices for the specified votes.
@@ -174,17 +251,14 @@ contract DisputeKit {
uint256 _choice,
uint256 _salt
) external {
- require(
- klerosCore.getCurrentPeriod(_disputeID) == KlerosCore.Period.vote,
- "The dispute should be in Vote period."
- );
- require(_voteIDs.length > 0);
+ require(core.getCurrentPeriod(_disputeID) == KlerosCore.Period.vote, "The dispute should be in Vote period.");
+ require(_voteIDs.length > 0, "No voteID provided");
Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]];
require(_choice <= dispute.numberOfChoices, "Choice out of bounds");
Round storage round = dispute.rounds[dispute.rounds.length - 1];
- bool hiddenVotes = klerosCore.areVotesHidden(klerosCore.getSubcourtID(_disputeID));
+ bool hiddenVotes = core.areVotesHidden(core.getSubcourtID(_disputeID));
// Save the votes.
for (uint256 i = 0; i < _voteIDs.length; i++) {
@@ -216,7 +290,7 @@ contract DisputeKit {
}
// Automatically switch period when voting is finished.
- if (round.totalVoted == round.votes.length) klerosCore.passPeriod(_disputeID);
+ if (round.totalVoted == round.votes.length) core.passPeriod(_disputeID);
}
/** @dev Manages contributions, and appeals a dispute if at least two choices are fully funded.
@@ -228,7 +302,7 @@ contract DisputeKit {
Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]];
require(_choice <= dispute.numberOfChoices, "There is no such ruling to fund.");
- (uint256 appealPeriodStart, uint256 appealPeriodEnd) = klerosCore.appealPeriod(_disputeID);
+ (uint256 appealPeriodStart, uint256 appealPeriodEnd) = core.appealPeriod(_disputeID);
require(block.timestamp >= appealPeriodStart && block.timestamp < appealPeriodEnd, "Appeal period is over.");
uint256 multiplier;
@@ -237,7 +311,7 @@ contract DisputeKit {
} else {
require(
block.timestamp - appealPeriodStart <
- ((appealPeriodEnd - appealPeriodStart) * LOSER_APPEAL_PERIOD_MULTIPLIER) / MULTIPLIER_DIVISOR,
+ ((appealPeriodEnd - appealPeriodStart) * LOSER_APPEAL_PERIOD_MULTIPLIER) / ONE_BASIS_POINT,
"Appeal period is over for loser"
);
multiplier = LOSER_STAKE_MULTIPLIER;
@@ -245,8 +319,8 @@ contract DisputeKit {
Round storage round = dispute.rounds[dispute.rounds.length - 1];
require(!round.hasPaid[_choice], "Appeal fee is already paid.");
- uint256 appealCost = klerosCore.appealCost(_disputeID);
- uint256 totalCost = appealCost + (appealCost * multiplier) / MULTIPLIER_DIVISOR;
+ uint256 appealCost = core.appealCost(_disputeID);
+ 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;
@@ -272,7 +346,7 @@ contract DisputeKit {
Round storage newRound = dispute.rounds.push();
newRound.tied = true;
- klerosCore.appeal{value: appealCost}(_disputeID);
+ core.appeal{value: appealCost}(_disputeID);
}
if (msg.value > contribution) payable(msg.sender).send(msg.value - contribution);
@@ -291,7 +365,7 @@ contract DisputeKit {
uint256 _round,
uint256 _choice
) external returns (uint256 amount) {
- require(klerosCore.isRuled(_disputeID), "Dispute should be resolved.");
+ require(core.isRuled(_disputeID), "Dispute should be resolved.");
Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]];
Round storage round = dispute.rounds[_round];
@@ -322,7 +396,9 @@ contract DisputeKit {
}
}
- /* Public views */
+ // ************************************* //
+ // * Public Views * //
+ // ************************************* //
/** @dev Gets the current ruling of a specified dispute.
* @param _disputeID The ID of the dispute in Kleros Core.
@@ -351,7 +427,7 @@ contract DisputeKit {
Vote storage vote = dispute.rounds[_round].votes[_voteID];
if (vote.voted && (vote.choice == lastRound.winningChoice || lastRound.tied)) {
- return 10000; // 1 in basis points.
+ return ONE_BASIS_POINT;
} else {
return 0;
}
@@ -393,25 +469,6 @@ contract DisputeKit {
return vote.voted;
}
- /** @dev Retrieves a juror's address from the stake path ID.
- * @param _stakePathID The stake path ID to unpack.
- * @return account The account.
- */
- function stakePathIDToAccount(bytes32 _stakePathID) internal pure returns (address account) {
- assembly {
- // solium-disable-line security/no-inline-assembly
- let ptr := mload(0x40)
- for {
- let i := 0x00
- } lt(i, 0x14) {
- i := add(i, 0x01)
- } {
- mstore8(add(add(ptr, 0x0c), i), byte(i, _stakePathID))
- }
- account := mload(ptr)
- }
- }
-
function getRoundInfo(
uint256 _disputeID,
uint256 _round,
@@ -458,4 +515,34 @@ contract DisputeKit {
Vote storage vote = dispute.rounds[_round].votes[_voteID];
return (vote.account, vote.commit, vote.choice, vote.voted);
}
+
+ // ************************************* //
+ // * Internal/Private * //
+ // ************************************* //
+
+ /** @dev RNG function
+ * @return rn A random number.
+ */
+ function getRandomNumber() private returns (uint256) {
+ return rng.getUncorrelatedRN(block.number);
+ }
+
+ /** @dev Retrieves a juror's address from the stake path ID.
+ * @param _stakePathID The stake path ID to unpack.
+ * @return account The account.
+ */
+ function stakePathIDToAccount(bytes32 _stakePathID) private pure returns (address account) {
+ assembly {
+ // solium-disable-line security/no-inline-assembly
+ let ptr := mload(0x40)
+ for {
+ let i := 0x00
+ } lt(i, 0x14) {
+ i := add(i, 0x01)
+ } {
+ mstore8(add(add(ptr, 0x0c), i), byte(i, _stakePathID))
+ }
+ account := mload(ptr)
+ }
+ }
}
diff --git a/contracts/src/arbitration/mock/MockKlerosCore.sol b/contracts/src/arbitration/mock/MockKlerosCore.sol
deleted file mode 100644
index 8f277593e..000000000
--- a/contracts/src/arbitration/mock/MockKlerosCore.sol
+++ /dev/null
@@ -1,162 +0,0 @@
-// SPDX-License-Identifier: MIT
-
-pragma solidity ^0.8;
-
-// import {SortitionSumTreeFactory} from "../../data-structures/SortitionSumTreeFactory.sol";
-import "../IArbitrable.sol";
-import "../AbstractDisputeKit.sol";
-
-contract MockKlerosCore {
- SortitionSumTrees internal sortitionSumTrees; // The sortition sum trees.
-
- 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.
- IArbitrable 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.
- // The votes in the form `votes[appeal][voteID]`. On each round, a new list is pushed and packed with as many empty votes as there are draws. We use `dispute.votes.length` to get the number of appeals plus 1 for the first round.
- // Vote[][] votes;
- //VoteCounter[] voteCounters; // The vote counters in the form `voteCounters[appeal]`.
- uint256[] tokensAtStakePerJuror; // The amount of tokens at stake for each juror in the form `tokensAtStakePerJuror[appeal]`.
- uint256[] totalFeesForJurors; // The total juror fees paid in the form `totalFeesForJurors[appeal]`.
- uint256 drawsInRound; // A counter of draws made in the current round.
- uint256 commitsInRound; // A counter of commits made in the current round.
- uint256[] votesInEachRound; // A counter of votes made in each round in the form `votesInEachRound[appeal]`.
- // A counter of vote reward repartitions made in each round in the form `repartitionsInEachRound[appeal]`.
- uint256[] repartitionsInEachRound;
- uint256[] penaltiesInEachRound; // The amount of tokens collected from penalties in each round in the form `penaltiesInEachRound[appeal]`.
- bool ruled; // True if the ruling has been executed, false otherwise.
- }
-
- struct Court {
- uint96 parent; // The parent court.
- uint256[] children; // List of child courts.
- bool hiddenVotes; // Whether to use commit and reveal or not.
- uint256 minStake; // Minimum tokens needed to stake in the court.
- uint256 alpha; // Basis point of tokens that are lost when incoherent.
- uint256 feeForJuror; // Arbitration fee paid per juror.
- // The appeal after the one that reaches this number of jurors will go to the parent court if any, otherwise, no more appeals are possible.
- uint256 jurorsForCourtJump;
- uint256[4] timesPerPeriod; // The time allotted to each dispute period in the form `timesPerPeriod[period]`.
- }
-
- event DisputeCreation(uint256 indexed _disputeID, IArbitrable indexed _arbitrable);
-
- uint256 public constant MIN_JURORS = 3; // The global default minimum number of jurors in a dispute.
-
- Dispute[] public disputes;
- Court[] public courts;
-
- AbstractDisputeKit disputeKit;
-
- constructor() {
- createTree(sortitionSumTrees, bytes32(0), 3);
- Court storage court = courts.push();
- court.minStake = 100;
- court.feeForJuror = 100;
- }
-
- // TODO: only owner
- function registerDisputeKit(AbstractDisputeKit _disputeKit) external {
- disputeKit = _disputeKit;
- }
-
- function createDispute(uint256 _choices, bytes calldata _extraData) external payable returns (uint256 disputeID) {
- (uint96 subcourtID, uint256 minJurors) = extraDataToSubcourtIDAndMinJurors(_extraData);
- disputeID = disputes.length;
-
- Court storage court = courts[0];
-
- disputeKit.createDispute(
- disputeID,
- msg.value,
- court.feeForJuror,
- court.minStake,
- court.alpha,
- _choices,
- _extraData
- );
-
- emit DisputeCreation(disputeID, IArbitrable(msg.sender));
- }
-
- function getSortitionSumTree(bytes32 _key)
- public
- view
- returns (
- uint256 k,
- uint256[] memory stack,
- uint256[] memory nodes
- )
- {
- SortitionSumTree storage tree = sortitionSumTrees.sortitionSumTrees[_key];
- k = tree.K;
- stack = tree.stack;
- nodes = tree.nodes;
- }
-
- function getSortitionSumTreeID(bytes32 _key, uint256 _nodeIndex) public view returns (bytes32 ID) {
- ID = sortitionSumTrees.sortitionSumTrees[_key].nodeIndexesToIDs[_nodeIndex];
- }
-
- function getDispute(uint256 _id) public view returns (Dispute memory) {
- return disputes[_id];
- }
-
- /** @dev Gets a subcourt ID and the minimum number of jurors required from a specified extra data bytes array.
- * @param _extraData The extra data bytes array. The first 32 bytes are the subcourt ID and the next 32 bytes are the minimum number of jurors.
- * @return subcourtID The subcourt ID.
- * @return minJurors The minimum number of jurors required.
- */
- function extraDataToSubcourtIDAndMinJurors(bytes memory _extraData)
- internal
- view
- returns (uint96 subcourtID, uint256 minJurors)
- {
- if (_extraData.length >= 64) {
- assembly {
- // solium-disable-line security/no-inline-assembly
- subcourtID := mload(add(_extraData, 0x20))
- minJurors := mload(add(_extraData, 0x40))
- }
- if (subcourtID >= courts.length) subcourtID = 0;
- if (minJurors == 0) minJurors = MIN_JURORS;
- } else {
- subcourtID = 0;
- minJurors = MIN_JURORS;
- }
- }
-
- // SORTITION TREE FACTORY
-
- struct SortitionSumTree {
- uint256 K; // The maximum number of childs per node.
- // We use this to keep track of vacant positions in the tree after removing a leaf. This is for keeping the tree as balanced as possible without spending gas on moving nodes around.
- uint256[] stack;
- uint256[] nodes;
- // Two-way mapping of IDs to node indexes. Note that node index 0 is reserved for the root node, and means the ID does not have a node.
- mapping(bytes32 => uint256) IDsToNodeIndexes;
- mapping(uint256 => bytes32) nodeIndexesToIDs;
- }
-
- struct SortitionSumTrees {
- mapping(bytes32 => SortitionSumTree) sortitionSumTrees;
- }
-
- function createTree(
- SortitionSumTrees storage self,
- bytes32 _key,
- uint256 _K
- ) private {
- SortitionSumTree storage tree = self.sortitionSumTrees[_key];
- require(tree.K == 0, "Tree already exists.");
- require(_K > 1, "K must be greater than one.");
- tree.K = _K;
- // tree.stack.length = 0;
- // tree.nodes.length = 0;
- tree.nodes.push(0);
- }
-}
diff --git a/contracts/test/arbitration/index.ts b/contracts/test/arbitration/index.ts
index 927c6ba88..adde4eced 100644
--- a/contracts/test/arbitration/index.ts
+++ b/contracts/test/arbitration/index.ts
@@ -7,10 +7,6 @@ const WINNER_STAKE_MULTIPLIER = 3000;
const LOSER_STAKE_MULTIPLIER = 7000;
const MULTIPLIER_DENOMINATOR = 10000;
-function sleep(ms) {
- return new Promise((resolve) => setTimeout(resolve, ms));
-}
-
describe("DisputeKitPlurality", function () {
// eslint-disable-next-line no-unused-vars
let deployer, claimant, supporter, challenger, innocentBystander;
@@ -19,41 +15,62 @@ describe("DisputeKitPlurality", function () {
before("Deploying", async () => {
[deployer, claimant, supporter, challenger, innocentBystander] = await ethers.getSigners();
[core, disputeKit, arbitrable] = await deployContracts(deployer);
-
- // To wait for eth gas reporter to fetch data. Remove this line when the issue is fixed. https://github.com/cgewecke/hardhat-gas-reporter/issues/72
- // await sleep(9000);
});
it("Should create a dispute", async function () {
- await expect(disputeKit.connect(deployer).createDispute(0, 0, 0, 0, 0, 0, "0x00")).to.be.revertedWith(
- "Not allowed: sender is not core"
+ await expect(disputeKit.connect(deployer).createDispute(0, 0, "0x00")).to.be.revertedWith(
+ "Access not allowed: KlerosCore only."
);
await expect(core.connect(deployer).createDispute(2, "0x00", { value: 1000 }))
.to.emit(core, "DisputeCreation")
.withArgs(0, deployer.address);
- console.log(await disputeKit.disputes(0));
- console.log(`votes=${await disputeKit.getVotes(0)}`);
- console.log(`votes=${await disputeKit.getVotesLength(0)}`);
- console.log(`voteCounter=${await disputeKit.getVoteCounter(0)}`);
+ await expect(BigNumber.from(Object.values(await disputeKit.disputes(0))[0])).to.equal(2);
+
+ console.log(`choice 0: ${await disputeKit.getRoundInfo(0, 0, 0)}`);
+ console.log(`choice 1: ${await disputeKit.getRoundInfo(0, 0, 1)}`);
+ console.log(`choice 2: ${await disputeKit.getRoundInfo(0, 0, 2)}`);
});
});
async function deployContracts(deployer) {
- const MockKlerosCoreFactory = await ethers.getContractFactory("MockKlerosCore", deployer);
- const core = await MockKlerosCoreFactory.deploy();
- await core.deployed();
-
const ConstantNGFactory = await ethers.getContractFactory("ConstantNG", deployer);
const rng = await ConstantNGFactory.deploy(42);
await rng.deployed();
- const disputeKitFactory = await ethers.getContractFactory("DisputeKitPlurality", deployer);
- const disputeKit = await disputeKitFactory.deploy(core.address, rng.address);
+ const disputeKitFactory = await ethers.getContractFactory("DisputeKit", deployer);
+ const disputeKit = await disputeKitFactory.deploy(
+ deployer.address,
+ ethers.constants.AddressZero,
+ rng.address
+ );
await disputeKit.deployed();
- await core.registerDisputeKit(disputeKit.address);
+ const SortitionSumTreeLibraryFactory = await ethers.getContractFactory("SortitionSumTreeFactory", deployer);
+ const library = await SortitionSumTreeLibraryFactory.deploy();
+
+ const KlerosCoreFactory = await ethers.getContractFactory("KlerosCore", {
+ signer: deployer,
+ libraries: {
+ SortitionSumTreeFactory: library.address,
+ },
+ });
+ const core = await KlerosCoreFactory.deploy(
+ deployer.address,
+ ethers.constants.AddressZero, // should be an ERC20
+ ethers.constants.AddressZero, // should be a Juror Prosecution module
+ disputeKit.address,
+ false,
+ 200,
+ 10000,
+ 100,
+ 3,
+ [0, 0, 0, 0],
+ 3);
+ await core.deployed();
+
+ await disputeKit.changeCore(core.address);
const ArbitrableFactory = await ethers.getContractFactory("ArbitrableExample", deployer);
const arbitrable = await ArbitrableFactory.deploy(core.address);
diff --git a/yarn.lock b/yarn.lock
index 3ba01750f..8b4a789d0 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1011,6 +1011,7 @@ __metadata:
hardhat: ^2.6.8
hardhat-deploy: ^0.9.6
hardhat-deploy-ethers: ^0.3.0-beta.11
+ hardhat-docgen: ^1.2.1
hardhat-gas-reporter: ^1.0.4
hardhat-watcher: ^2.1.1
json-schema: ^0.4.0
@@ -1452,6 +1453,33 @@ __metadata:
languageName: node
linkType: hard
+"@types/eslint-scope@npm:^3.7.0":
+ version: 3.7.2
+ resolution: "@types/eslint-scope@npm:3.7.2"
+ dependencies:
+ "@types/eslint": "*"
+ "@types/estree": "*"
+ checksum: 7ce2b4a07c22e7b265d4ee145196fcf00993b8aaeecaf5cecc8231c820a000c00bfaee6c026a2f363c215822c8fbf5dbedb2d3f56621cdda87a6601db2a05319
+ languageName: node
+ linkType: hard
+
+"@types/eslint@npm:*":
+ version: 8.2.1
+ resolution: "@types/eslint@npm:8.2.1"
+ dependencies:
+ "@types/estree": "*"
+ "@types/json-schema": "*"
+ checksum: f32753ba184c212056f2bb7ee16937150a36e01da7eed15e2e179b7df76d0bbcbfa49972f30e9336f22be471c7f67fd91bcc8c25ff532462598de0f489df0cd8
+ languageName: node
+ linkType: hard
+
+"@types/estree@npm:*, @types/estree@npm:^0.0.50":
+ version: 0.0.50
+ resolution: "@types/estree@npm:0.0.50"
+ checksum: 9a2b6a4a8c117f34d08fbda5e8f69b1dfb109f7d149b60b00fd7a9fb6ac545c078bc590aa4ec2f0a256d680cf72c88b3b28b60c326ee38a7bc8ee1ee95624922
+ languageName: node
+ linkType: hard
+
"@types/form-data@npm:0.0.33":
version: 0.0.33
resolution: "@types/form-data@npm:0.0.33"
@@ -1471,7 +1499,14 @@ __metadata:
languageName: node
linkType: hard
-"@types/json-schema@npm:^7.0.7":
+"@types/html-minifier-terser@npm:^6.0.0":
+ version: 6.1.0
+ resolution: "@types/html-minifier-terser@npm:6.1.0"
+ checksum: eb843f6a8d662d44fb18ec61041117734c6aae77aa38df1be3b4712e8e50ffaa35f1e1c92fdd0fde14a5675fecf457abcd0d15a01fae7506c91926176967f452
+ languageName: node
+ linkType: hard
+
+"@types/json-schema@npm:*, @types/json-schema@npm:^7.0.7, @types/json-schema@npm:^7.0.8":
version: 7.0.9
resolution: "@types/json-schema@npm:7.0.9"
checksum: 259d0e25f11a21ba5c708f7ea47196bd396e379fddb79c76f9f4f62c945879dc21657904914313ec2754e443c5018ea8372362f323f30e0792897fdb2098a705
@@ -1844,6 +1879,26 @@ __metadata:
languageName: node
linkType: hard
+"@vue/component-compiler-utils@npm:^3.1.0":
+ version: 3.3.0
+ resolution: "@vue/component-compiler-utils@npm:3.3.0"
+ dependencies:
+ consolidate: ^0.15.1
+ hash-sum: ^1.0.2
+ lru-cache: ^4.1.2
+ merge-source-map: ^1.1.0
+ postcss: ^7.0.36
+ postcss-selector-parser: ^6.0.2
+ prettier: ^1.18.2 || ^2.0.0
+ source-map: ~0.6.1
+ vue-template-es2015-compiler: ^1.9.0
+ dependenciesMeta:
+ prettier:
+ optional: true
+ checksum: 70fee2289a4f54ec1be4d46136ee9b9893e31bf5622cead5be06c3dfb83449c3dbe6f8c03404625ccf302d0628ff9e2ea1debfae609d1bfe1d065d8f57c5dba8
+ languageName: node
+ linkType: hard
+
"@vue/ref-transform@npm:3.2.21":
version: 3.2.21
resolution: "@vue/ref-transform@npm:3.2.21"
@@ -1864,6 +1919,171 @@ __metadata:
languageName: node
linkType: hard
+"@webassemblyjs/ast@npm:1.11.1":
+ version: 1.11.1
+ resolution: "@webassemblyjs/ast@npm:1.11.1"
+ dependencies:
+ "@webassemblyjs/helper-numbers": 1.11.1
+ "@webassemblyjs/helper-wasm-bytecode": 1.11.1
+ checksum: 1eee1534adebeece635362f8e834ae03e389281972611408d64be7895fc49f48f98fddbbb5339bf8a72cb101bcb066e8bca3ca1bf1ef47dadf89def0395a8d87
+ languageName: node
+ linkType: hard
+
+"@webassemblyjs/floating-point-hex-parser@npm:1.11.1":
+ version: 1.11.1
+ resolution: "@webassemblyjs/floating-point-hex-parser@npm:1.11.1"
+ checksum: b8efc6fa08e4787b7f8e682182d84dfdf8da9d9c77cae5d293818bc4a55c1f419a87fa265ab85252b3e6c1fd323d799efea68d825d341a7c365c64bc14750e97
+ languageName: node
+ linkType: hard
+
+"@webassemblyjs/helper-api-error@npm:1.11.1":
+ version: 1.11.1
+ resolution: "@webassemblyjs/helper-api-error@npm:1.11.1"
+ checksum: 0792813f0ed4a0e5ee0750e8b5d0c631f08e927f4bdfdd9fe9105dc410c786850b8c61bff7f9f515fdfb149903bec3c976a1310573a4c6866a94d49bc7271959
+ languageName: node
+ linkType: hard
+
+"@webassemblyjs/helper-buffer@npm:1.11.1":
+ version: 1.11.1
+ resolution: "@webassemblyjs/helper-buffer@npm:1.11.1"
+ checksum: a337ee44b45590c3a30db5a8b7b68a717526cf967ada9f10253995294dbd70a58b2da2165222e0b9830cd4fc6e4c833bf441a721128d1fe2e9a7ab26b36003ce
+ languageName: node
+ linkType: hard
+
+"@webassemblyjs/helper-numbers@npm:1.11.1":
+ version: 1.11.1
+ resolution: "@webassemblyjs/helper-numbers@npm:1.11.1"
+ dependencies:
+ "@webassemblyjs/floating-point-hex-parser": 1.11.1
+ "@webassemblyjs/helper-api-error": 1.11.1
+ "@xtuc/long": 4.2.2
+ checksum: 44d2905dac2f14d1e9b5765cf1063a0fa3d57295c6d8930f6c59a36462afecc6e763e8a110b97b342a0f13376166c5d41aa928e6ced92e2f06b071fd0db59d3a
+ languageName: node
+ linkType: hard
+
+"@webassemblyjs/helper-wasm-bytecode@npm:1.11.1":
+ version: 1.11.1
+ resolution: "@webassemblyjs/helper-wasm-bytecode@npm:1.11.1"
+ checksum: eac400113127832c88f5826bcc3ad1c0db9b3dbd4c51a723cfdb16af6bfcbceb608170fdaac0ab7731a7e18b291be7af68a47fcdb41cfe0260c10857e7413d97
+ languageName: node
+ linkType: hard
+
+"@webassemblyjs/helper-wasm-section@npm:1.11.1":
+ version: 1.11.1
+ resolution: "@webassemblyjs/helper-wasm-section@npm:1.11.1"
+ dependencies:
+ "@webassemblyjs/ast": 1.11.1
+ "@webassemblyjs/helper-buffer": 1.11.1
+ "@webassemblyjs/helper-wasm-bytecode": 1.11.1
+ "@webassemblyjs/wasm-gen": 1.11.1
+ checksum: 617696cfe8ecaf0532763162aaf748eb69096fb27950219bb87686c6b2e66e11cd0614d95d319d0ab1904bc14ebe4e29068b12c3e7c5e020281379741fe4bedf
+ languageName: node
+ linkType: hard
+
+"@webassemblyjs/ieee754@npm:1.11.1":
+ version: 1.11.1
+ resolution: "@webassemblyjs/ieee754@npm:1.11.1"
+ dependencies:
+ "@xtuc/ieee754": ^1.2.0
+ checksum: 23a0ac02a50f244471631802798a816524df17e56b1ef929f0c73e3cde70eaf105a24130105c60aff9d64a24ce3b640dad443d6f86e5967f922943a7115022ec
+ languageName: node
+ linkType: hard
+
+"@webassemblyjs/leb128@npm:1.11.1":
+ version: 1.11.1
+ resolution: "@webassemblyjs/leb128@npm:1.11.1"
+ dependencies:
+ "@xtuc/long": 4.2.2
+ checksum: 33ccc4ade2f24de07bf31690844d0b1ad224304ee2062b0e464a610b0209c79e0b3009ac190efe0e6bd568b0d1578d7c3047fc1f9d0197c92fc061f56224ff4a
+ languageName: node
+ linkType: hard
+
+"@webassemblyjs/utf8@npm:1.11.1":
+ version: 1.11.1
+ resolution: "@webassemblyjs/utf8@npm:1.11.1"
+ checksum: 972c5cfc769d7af79313a6bfb96517253a270a4bf0c33ba486aa43cac43917184fb35e51dfc9e6b5601548cd5931479a42e42c89a13bb591ffabebf30c8a6a0b
+ languageName: node
+ linkType: hard
+
+"@webassemblyjs/wasm-edit@npm:1.11.1":
+ version: 1.11.1
+ resolution: "@webassemblyjs/wasm-edit@npm:1.11.1"
+ dependencies:
+ "@webassemblyjs/ast": 1.11.1
+ "@webassemblyjs/helper-buffer": 1.11.1
+ "@webassemblyjs/helper-wasm-bytecode": 1.11.1
+ "@webassemblyjs/helper-wasm-section": 1.11.1
+ "@webassemblyjs/wasm-gen": 1.11.1
+ "@webassemblyjs/wasm-opt": 1.11.1
+ "@webassemblyjs/wasm-parser": 1.11.1
+ "@webassemblyjs/wast-printer": 1.11.1
+ checksum: 6d7d9efaec1227e7ef7585a5d7ff0be5f329f7c1c6b6c0e906b18ed2e9a28792a5635e450aca2d136770d0207225f204eff70a4b8fd879d3ac79e1dcc26dbeb9
+ languageName: node
+ linkType: hard
+
+"@webassemblyjs/wasm-gen@npm:1.11.1":
+ version: 1.11.1
+ resolution: "@webassemblyjs/wasm-gen@npm:1.11.1"
+ dependencies:
+ "@webassemblyjs/ast": 1.11.1
+ "@webassemblyjs/helper-wasm-bytecode": 1.11.1
+ "@webassemblyjs/ieee754": 1.11.1
+ "@webassemblyjs/leb128": 1.11.1
+ "@webassemblyjs/utf8": 1.11.1
+ checksum: 1f6921e640293bf99fb16b21e09acb59b340a79f986c8f979853a0ae9f0b58557534b81e02ea2b4ef11e929d946708533fd0693c7f3712924128fdafd6465f5b
+ languageName: node
+ linkType: hard
+
+"@webassemblyjs/wasm-opt@npm:1.11.1":
+ version: 1.11.1
+ resolution: "@webassemblyjs/wasm-opt@npm:1.11.1"
+ dependencies:
+ "@webassemblyjs/ast": 1.11.1
+ "@webassemblyjs/helper-buffer": 1.11.1
+ "@webassemblyjs/wasm-gen": 1.11.1
+ "@webassemblyjs/wasm-parser": 1.11.1
+ checksum: 21586883a20009e2b20feb67bdc451bbc6942252e038aae4c3a08e6f67b6bae0f5f88f20bfc7bd0452db5000bacaf5ab42b98cf9aa034a6c70e9fc616142e1db
+ languageName: node
+ linkType: hard
+
+"@webassemblyjs/wasm-parser@npm:1.11.1":
+ version: 1.11.1
+ resolution: "@webassemblyjs/wasm-parser@npm:1.11.1"
+ dependencies:
+ "@webassemblyjs/ast": 1.11.1
+ "@webassemblyjs/helper-api-error": 1.11.1
+ "@webassemblyjs/helper-wasm-bytecode": 1.11.1
+ "@webassemblyjs/ieee754": 1.11.1
+ "@webassemblyjs/leb128": 1.11.1
+ "@webassemblyjs/utf8": 1.11.1
+ checksum: 1521644065c360e7b27fad9f4bb2df1802d134dd62937fa1f601a1975cde56bc31a57b6e26408b9ee0228626ff3ba1131ae6f74ffb7d718415b6528c5a6dbfc2
+ languageName: node
+ linkType: hard
+
+"@webassemblyjs/wast-printer@npm:1.11.1":
+ version: 1.11.1
+ resolution: "@webassemblyjs/wast-printer@npm:1.11.1"
+ dependencies:
+ "@webassemblyjs/ast": 1.11.1
+ "@xtuc/long": 4.2.2
+ checksum: f15ae4c2441b979a3b4fce78f3d83472fb22350c6dc3fd34bfe7c3da108e0b2360718734d961bba20e7716cb8578e964b870da55b035e209e50ec9db0378a3f7
+ languageName: node
+ linkType: hard
+
+"@xtuc/ieee754@npm:^1.2.0":
+ version: 1.2.0
+ resolution: "@xtuc/ieee754@npm:1.2.0"
+ checksum: ac56d4ca6e17790f1b1677f978c0c6808b1900a5b138885d3da21732f62e30e8f0d9120fcf8f6edfff5100ca902b46f8dd7c1e3f903728634523981e80e2885a
+ languageName: node
+ linkType: hard
+
+"@xtuc/long@npm:4.2.2":
+ version: 4.2.2
+ resolution: "@xtuc/long@npm:4.2.2"
+ checksum: 8ed0d477ce3bc9c6fe2bf6a6a2cc316bb9c4127c5a7827bae947fa8ec34c7092395c5a283cc300c05b5fa01cbbfa1f938f410a7bf75db7c7846fea41949989ec
+ languageName: node
+ linkType: hard
+
"@yarnpkg/lockfile@npm:^1.1.0":
version: 1.1.0
resolution: "@yarnpkg/lockfile@npm:1.1.0"
@@ -1978,6 +2198,15 @@ __metadata:
languageName: node
linkType: hard
+"acorn-import-assertions@npm:^1.7.6":
+ version: 1.8.0
+ resolution: "acorn-import-assertions@npm:1.8.0"
+ peerDependencies:
+ acorn: ^8
+ checksum: 5c4cf7c850102ba7ae0eeae0deb40fb3158c8ca5ff15c0bca43b5c47e307a1de3d8ef761788f881343680ea374631ae9e9615ba8876fee5268dbe068c98bcba6
+ languageName: node
+ linkType: hard
+
"acorn-jsx@npm:^5.0.0, acorn-jsx@npm:^5.3.1":
version: 5.3.2
resolution: "acorn-jsx@npm:5.3.2"
@@ -2086,7 +2315,16 @@ __metadata:
languageName: node
linkType: hard
-"ajv@npm:^6.10.0, ajv@npm:^6.10.2, ajv@npm:^6.12.3, ajv@npm:^6.12.4, ajv@npm:^6.6.1, ajv@npm:^6.9.1":
+"ajv-keywords@npm:^3.5.2":
+ version: 3.5.2
+ resolution: "ajv-keywords@npm:3.5.2"
+ peerDependencies:
+ ajv: ^6.9.1
+ checksum: 7dc5e5931677a680589050f79dcbe1fefbb8fea38a955af03724229139175b433c63c68f7ae5f86cf8f65d55eb7c25f75a046723e2e58296707617ca690feae9
+ languageName: node
+ linkType: hard
+
+"ajv@npm:^6.10.0, ajv@npm:^6.10.2, ajv@npm:^6.12.3, ajv@npm:^6.12.4, ajv@npm:^6.12.5, ajv@npm:^6.6.1, ajv@npm:^6.9.1":
version: 6.12.6
resolution: "ajv@npm:6.12.6"
dependencies:
@@ -3232,6 +3470,13 @@ __metadata:
languageName: node
linkType: hard
+"big.js@npm:^5.2.2":
+ version: 5.2.2
+ resolution: "big.js@npm:5.2.2"
+ checksum: b89b6e8419b097a8fb4ed2399a1931a68c612bce3cfd5ca8c214b2d017531191070f990598de2fc6f3f993d91c0f08aa82697717f6b3b8732c9731866d233c9e
+ languageName: node
+ linkType: hard
+
"bignumber.js@npm:^9.0.0, bignumber.js@npm:^9.0.1":
version: 9.0.1
resolution: "bignumber.js@npm:9.0.1"
@@ -3284,7 +3529,7 @@ __metadata:
languageName: node
linkType: hard
-"bluebird@npm:^3.5.0, bluebird@npm:^3.5.2":
+"bluebird@npm:^3.1.1, bluebird@npm:^3.5.0, bluebird@npm:^3.5.2":
version: 3.7.2
resolution: "bluebird@npm:3.7.2"
checksum: 869417503c722e7dc54ca46715f70e15f4d9c602a423a02c825570862d12935be59ed9c7ba34a9b31f186c017c23cac6b54e35446f8353059c101da73eac22ef
@@ -3330,6 +3575,13 @@ __metadata:
languageName: node
linkType: hard
+"boolbase@npm:^1.0.0":
+ version: 1.0.0
+ resolution: "boolbase@npm:1.0.0"
+ checksum: 3e25c80ef626c3a3487c73dbfc70ac322ec830666c9ad915d11b701142fab25ec1e63eff2c450c74347acfd2de854ccde865cd79ef4db1683f7c7b046ea43bb0
+ languageName: node
+ linkType: hard
+
"brace-expansion@npm:^1.1.7":
version: 1.1.11
resolution: "brace-expansion@npm:1.1.11"
@@ -3457,6 +3709,21 @@ __metadata:
languageName: node
linkType: hard
+"browserslist@npm:^4.14.5":
+ version: 4.19.1
+ resolution: "browserslist@npm:4.19.1"
+ dependencies:
+ caniuse-lite: ^1.0.30001286
+ electron-to-chromium: ^1.4.17
+ escalade: ^3.1.1
+ node-releases: ^2.0.1
+ picocolors: ^1.0.0
+ bin:
+ browserslist: cli.js
+ checksum: c0777fd483691638fd6801e16c9d809e1d65f6d2b06db2e806654be51045cbab1452a89841a2c5caea2cbe19d621b4f1d391cffbb24512aa33280039ab345875
+ languageName: node
+ linkType: hard
+
"bs58@npm:^4.0.0":
version: 4.0.1
resolution: "bs58@npm:4.0.1"
@@ -3663,6 +3930,16 @@ __metadata:
languageName: node
linkType: hard
+"camel-case@npm:^4.1.2":
+ version: 4.1.2
+ resolution: "camel-case@npm:4.1.2"
+ dependencies:
+ pascal-case: ^3.1.2
+ tslib: ^2.0.3
+ checksum: bcbd25cd253b3cbc69be3f535750137dbf2beb70f093bdc575f73f800acc8443d34fd52ab8f0a2413c34f1e8203139ffc88428d8863e4dfe530cfb257a379ad6
+ languageName: node
+ linkType: hard
+
"camelcase-keys@npm:^6.2.2":
version: 6.2.2
resolution: "camelcase-keys@npm:6.2.2"
@@ -3702,6 +3979,13 @@ __metadata:
languageName: node
linkType: hard
+"caniuse-lite@npm:^1.0.30001286":
+ version: 1.0.30001294
+ resolution: "caniuse-lite@npm:1.0.30001294"
+ checksum: 4e22649ef83781afbe6c81d6112ceac23c3ba29f91597ca1c14d323d5bfb646571edeb17436e76a29b07c8e9d75468655a2098a3e13dcc1438db47ff46fbb3ad
+ languageName: node
+ linkType: hard
+
"caseless@npm:^0.12.0, caseless@npm:~0.12.0":
version: 0.12.0
resolution: "caseless@npm:0.12.0"
@@ -3849,6 +4133,13 @@ __metadata:
languageName: node
linkType: hard
+"chrome-trace-event@npm:^1.0.2":
+ version: 1.0.3
+ resolution: "chrome-trace-event@npm:1.0.3"
+ checksum: cb8b1fc7e881aaef973bd0c4a43cd353c2ad8323fb471a041e64f7c2dd849cde4aad15f8b753331a32dda45c973f032c8a03b8177fc85d60eaa75e91e08bfb97
+ languageName: node
+ linkType: hard
+
"ci-info@npm:^2.0.0":
version: 2.0.0
resolution: "ci-info@npm:2.0.0"
@@ -3898,6 +4189,15 @@ __metadata:
languageName: node
linkType: hard
+"clean-css@npm:^5.2.2":
+ version: 5.2.2
+ resolution: "clean-css@npm:5.2.2"
+ dependencies:
+ source-map: ~0.6.0
+ checksum: 10855820829b8b6ea94e462313fdc177b297aca5c7870a969591549d6a766824f912b5e58773bd345b2a7effae863ab492258b5a77a40029fba6d11d861cbee3
+ languageName: node
+ linkType: hard
+
"clean-stack@npm:^2.0.0":
version: 2.2.0
resolution: "clean-stack@npm:2.2.0"
@@ -4116,7 +4416,14 @@ __metadata:
languageName: node
linkType: hard
-"commander@npm:^8.2.0":
+"commander@npm:^2.20.0":
+ version: 2.20.3
+ resolution: "commander@npm:2.20.3"
+ checksum: ab8c07884e42c3a8dbc5dd9592c606176c7eb5c1ca5ff274bcf907039b2c41de3626f684ea75ccf4d361ba004bbaff1f577d5384c155f3871e456bdf27becf9e
+ languageName: node
+ linkType: hard
+
+"commander@npm:^8.2.0, commander@npm:^8.3.0":
version: 8.3.0
resolution: "commander@npm:8.3.0"
checksum: 0f82321821fc27b83bd409510bb9deeebcfa799ff0bf5d102128b500b7af22872c0c92cb6a0ebc5a4cf19c6b550fba9cedfa7329d18c6442a625f851377bacf0
@@ -4166,6 +4473,15 @@ __metadata:
languageName: node
linkType: hard
+"consolidate@npm:^0.15.1":
+ version: 0.15.1
+ resolution: "consolidate@npm:0.15.1"
+ dependencies:
+ bluebird: ^3.1.1
+ checksum: 5a44ee975f8403dd3ff8ff3472fda7db0484b19f153eaac38e784465505a0741939c72d703befda7c75649739fc7a68f9659a86e2a62469336a8d531bd7a10df
+ languageName: node
+ linkType: hard
+
"content-disposition@npm:0.5.3":
version: 0.5.3
resolution: "content-disposition@npm:0.5.3"
@@ -4609,6 +4925,53 @@ __metadata:
languageName: node
linkType: hard
+"css-loader@npm:^6.5.1":
+ version: 6.5.1
+ resolution: "css-loader@npm:6.5.1"
+ dependencies:
+ icss-utils: ^5.1.0
+ postcss: ^8.2.15
+ postcss-modules-extract-imports: ^3.0.0
+ postcss-modules-local-by-default: ^4.0.0
+ postcss-modules-scope: ^3.0.0
+ postcss-modules-values: ^4.0.0
+ postcss-value-parser: ^4.1.0
+ semver: ^7.3.5
+ peerDependencies:
+ webpack: ^5.0.0
+ checksum: 5a3bedecb468038f09673d25c32d8db5b0baa6c38820253c54ce4c56c27a2250d5d5b4bace77dd5e20ba0a569604eb759362bab4e3128e7db2229e40857d4aca
+ languageName: node
+ linkType: hard
+
+"css-select@npm:^4.1.3":
+ version: 4.2.1
+ resolution: "css-select@npm:4.2.1"
+ dependencies:
+ boolbase: ^1.0.0
+ css-what: ^5.1.0
+ domhandler: ^4.3.0
+ domutils: ^2.8.0
+ nth-check: ^2.0.1
+ checksum: 6617193ec7c332217204c4ea371d332c6845603fda415e36032e7e9e18206d7c368a14e3c57532082314d2689955b01122aa1097c1c52b6c1cab7ad90970d3c6
+ languageName: node
+ linkType: hard
+
+"css-what@npm:^5.1.0":
+ version: 5.1.0
+ resolution: "css-what@npm:5.1.0"
+ checksum: 0b75d1bac95c885c168573c85744a6c6843d8c33345f54f717218b37ea6296b0e99bb12105930ea170fd4a921990392a7c790c16c585c1d8960c49e2b7ec39f7
+ languageName: node
+ linkType: hard
+
+"cssesc@npm:^3.0.0":
+ version: 3.0.0
+ resolution: "cssesc@npm:3.0.0"
+ bin:
+ cssesc: bin/cssesc
+ checksum: f8c4ababffbc5e2ddf2fa9957dda1ee4af6048e22aeda1869d0d00843223c1b13ad3f5d88b51caa46c994225eacb636b764eb807a8883e2fb6f99b4f4e8c48b2
+ languageName: node
+ linkType: hard
+
"d@npm:1, d@npm:^1.0.1":
version: 1.0.1
resolution: "d@npm:1.0.1"
@@ -4642,6 +5005,13 @@ __metadata:
languageName: node
linkType: hard
+"de-indent@npm:^1.0.2":
+ version: 1.0.2
+ resolution: "de-indent@npm:1.0.2"
+ checksum: 8deacc0f4a397a4414a0fc4d0034d2b7782e7cb4eaf34943ea47754e08eccf309a0e71fa6f56cc48de429ede999a42d6b4bca761bf91683be0095422dbf24611
+ languageName: node
+ linkType: hard
+
"death@npm:^1.1.0":
version: 1.1.0
resolution: "death@npm:1.1.0"
@@ -4997,6 +5367,26 @@ __metadata:
languageName: node
linkType: hard
+"dom-converter@npm:^0.2.0":
+ version: 0.2.0
+ resolution: "dom-converter@npm:0.2.0"
+ dependencies:
+ utila: ~0.4
+ checksum: ea52fe303f5392e48dea563abef0e6fb3a478b8dbe3c599e99bb5d53981c6c38fc4944e56bb92a8ead6bb989d10b7914722ae11febbd2fd0910e33b9fc4aaa77
+ languageName: node
+ linkType: hard
+
+"dom-serializer@npm:^1.0.1":
+ version: 1.3.2
+ resolution: "dom-serializer@npm:1.3.2"
+ dependencies:
+ domelementtype: ^2.0.1
+ domhandler: ^4.2.0
+ entities: ^2.0.0
+ checksum: bff48714944d67b160db71ba244fb0f3fe72e77ef2ec8414e2eeb56f2d926e404a13456b8b83a5392e217ba47dec2ec0c368801b31481813e94d185276c3e964
+ languageName: node
+ linkType: hard
+
"dom-walk@npm:^0.1.0":
version: 0.1.2
resolution: "dom-walk@npm:0.1.2"
@@ -5004,6 +5394,43 @@ __metadata:
languageName: node
linkType: hard
+"domelementtype@npm:^2.0.1, domelementtype@npm:^2.2.0":
+ version: 2.2.0
+ resolution: "domelementtype@npm:2.2.0"
+ checksum: 24cb386198640cd58aa36f8c987f2ea61859929106d06ffcc8f547e70cb2ed82a6dc56dcb8252b21fba1f1ea07df6e4356d60bfe57f77114ca1aed6828362629
+ languageName: node
+ linkType: hard
+
+"domhandler@npm:^4.0.0, domhandler@npm:^4.2.0, domhandler@npm:^4.3.0":
+ version: 4.3.0
+ resolution: "domhandler@npm:4.3.0"
+ dependencies:
+ domelementtype: ^2.2.0
+ checksum: d2a2dbf40dd99abf936b65ad83c6b530afdb3605a87cad37a11b5d9220e68423ebef1b86c89e0f6d93ffaf315cc327cf1a988652e7a9a95cce539e3984f4c64d
+ languageName: node
+ linkType: hard
+
+"domutils@npm:^2.5.2, domutils@npm:^2.8.0":
+ version: 2.8.0
+ resolution: "domutils@npm:2.8.0"
+ dependencies:
+ dom-serializer: ^1.0.1
+ domelementtype: ^2.2.0
+ domhandler: ^4.2.0
+ checksum: abf7434315283e9aadc2a24bac0e00eab07ae4313b40cc239f89d84d7315ebdfd2fb1b5bf750a96bc1b4403d7237c7b2ebf60459be394d625ead4ca89b934391
+ languageName: node
+ linkType: hard
+
+"dot-case@npm:^3.0.4":
+ version: 3.0.4
+ resolution: "dot-case@npm:3.0.4"
+ dependencies:
+ no-case: ^3.0.4
+ tslib: ^2.0.3
+ checksum: a65e3519414856df0228b9f645332f974f2bf5433370f544a681122eab59e66038fc3349b4be1cdc47152779dac71a5864f1ccda2f745e767c46e9c6543b1169
+ languageName: node
+ linkType: hard
+
"dot-prop@npm:^5.1.0":
version: 5.3.0
resolution: "dot-prop@npm:5.3.0"
@@ -5073,6 +5500,13 @@ __metadata:
languageName: node
linkType: hard
+"electron-to-chromium@npm:^1.4.17":
+ version: 1.4.29
+ resolution: "electron-to-chromium@npm:1.4.29"
+ checksum: 4e80353d29218991b2d1e1219ce4a2a2da0a9fa79d71956d21d00cd735f5961d068ddee2d2010135f41a46a592a43dea8fe09dd81ea40c8309d2a23847655da4
+ languageName: node
+ linkType: hard
+
"elliptic@npm:6.5.4, elliptic@npm:^6.4.0, elliptic@npm:^6.5.2, elliptic@npm:^6.5.3":
version: 6.5.4
resolution: "elliptic@npm:6.5.4"
@@ -5109,6 +5543,13 @@ __metadata:
languageName: node
linkType: hard
+"emojis-list@npm:^3.0.0":
+ version: 3.0.0
+ resolution: "emojis-list@npm:3.0.0"
+ checksum: ddaaa02542e1e9436c03970eeed445f4ed29a5337dfba0fe0c38dfdd2af5da2429c2a0821304e8a8d1cadf27fdd5b22ff793571fa803ae16852a6975c65e8e70
+ languageName: node
+ linkType: hard
+
"encode-utf8@npm:^1.0.2":
version: 1.0.3
resolution: "encode-utf8@npm:1.0.3"
@@ -5166,6 +5607,16 @@ __metadata:
languageName: node
linkType: hard
+"enhanced-resolve@npm:^5.8.3":
+ version: 5.8.3
+ resolution: "enhanced-resolve@npm:5.8.3"
+ dependencies:
+ graceful-fs: ^4.2.4
+ tapable: ^2.2.0
+ checksum: d79fbe531106448b768bb0673fb623ec0202d7ee70373ab7d4f4745d5dfe0806f38c9db7e7da8c941288fe475ab3d538db3791fce522056eeea40ca398c9e287
+ languageName: node
+ linkType: hard
+
"enquirer@npm:^2.3.0, enquirer@npm:^2.3.5, enquirer@npm:^2.3.6":
version: 2.3.6
resolution: "enquirer@npm:2.3.6"
@@ -5175,6 +5626,13 @@ __metadata:
languageName: node
linkType: hard
+"entities@npm:^2.0.0":
+ version: 2.2.0
+ resolution: "entities@npm:2.2.0"
+ checksum: 19010dacaf0912c895ea262b4f6128574f9ccf8d4b3b65c7e8334ad0079b3706376360e28d8843ff50a78aabcb8f08f0a32dbfacdc77e47ed77ca08b713669b3
+ languageName: node
+ linkType: hard
+
"env-paths@npm:^2.2.0":
version: 2.2.1
resolution: "env-paths@npm:2.2.1"
@@ -5237,6 +5695,13 @@ __metadata:
languageName: node
linkType: hard
+"es-module-lexer@npm:^0.9.0":
+ version: 0.9.3
+ resolution: "es-module-lexer@npm:0.9.3"
+ checksum: 84bbab23c396281db2c906c766af58b1ae2a1a2599844a504df10b9e8dc77ec800b3211fdaa133ff700f5703d791198807bba25d9667392d27a5e9feda344da8
+ languageName: node
+ linkType: hard
+
"es-to-primitive@npm:^1.2.1":
version: 1.2.1
resolution: "es-to-primitive@npm:1.2.1"
@@ -5455,23 +5920,23 @@ __metadata:
languageName: node
linkType: hard
-"eslint-scope@npm:^4.0.3":
- version: 4.0.3
- resolution: "eslint-scope@npm:4.0.3"
+"eslint-scope@npm:5.1.1, eslint-scope@npm:^5.1.1":
+ version: 5.1.1
+ resolution: "eslint-scope@npm:5.1.1"
dependencies:
- esrecurse: ^4.1.0
+ esrecurse: ^4.3.0
estraverse: ^4.1.1
- checksum: c5f835f681884469991fe58d76a554688d9c9e50811299ccd4a8f79993a039f5bcb0ee6e8de2b0017d97c794b5832ef3b21c9aac66228e3aa0f7a0485bcfb65b
+ checksum: 47e4b6a3f0cc29c7feedee6c67b225a2da7e155802c6ea13bbef4ac6b9e10c66cd2dcb987867ef176292bf4e64eccc680a49e35e9e9c669f4a02bac17e86abdb
languageName: node
linkType: hard
-"eslint-scope@npm:^5.1.1":
- version: 5.1.1
- resolution: "eslint-scope@npm:5.1.1"
+"eslint-scope@npm:^4.0.3":
+ version: 4.0.3
+ resolution: "eslint-scope@npm:4.0.3"
dependencies:
- esrecurse: ^4.3.0
+ esrecurse: ^4.1.0
estraverse: ^4.1.1
- checksum: 47e4b6a3f0cc29c7feedee6c67b225a2da7e155802c6ea13bbef4ac6b9e10c66cd2dcb987867ef176292bf4e64eccc680a49e35e9e9c669f4a02bac17e86abdb
+ checksum: c5f835f681884469991fe58d76a554688d9c9e50811299ccd4a8f79993a039f5bcb0ee6e8de2b0017d97c794b5832ef3b21c9aac66228e3aa0f7a0485bcfb65b
languageName: node
linkType: hard
@@ -6310,7 +6775,7 @@ __metadata:
languageName: node
linkType: hard
-"events@npm:^3.0.0":
+"events@npm:^3.0.0, events@npm:^3.2.0":
version: 3.3.0
resolution: "events@npm:3.3.0"
checksum: f6f487ad2198aa41d878fa31452f1a3c00958f46e9019286ff4787c84aac329332ab45c9cdc8c445928fc6d7ded294b9e005a7fce9426488518017831b272780
@@ -7313,6 +7778,13 @@ __metadata:
languageName: node
linkType: hard
+"glob-to-regexp@npm:^0.4.1":
+ version: 0.4.1
+ resolution: "glob-to-regexp@npm:0.4.1"
+ checksum: e795f4e8f06d2a15e86f76e4d92751cf8bbfcf0157cea5c2f0f35678a8195a750b34096b1256e436f0cebc1883b5ff0888c47348443e69546a5a87f9e1eb1167
+ languageName: node
+ linkType: hard
+
"glob@npm:7.1.3":
version: 7.1.3
resolution: "glob@npm:7.1.3"
@@ -7501,7 +7973,7 @@ __metadata:
languageName: node
linkType: hard
-"graceful-fs@npm:^4.1.11, graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.1.9, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.6":
+"graceful-fs@npm:^4.1.11, graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.1.9, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6":
version: 4.2.8
resolution: "graceful-fs@npm:4.2.8"
checksum: 5d224c8969ad0581d551dfabdb06882706b31af2561bd5e2034b4097e67cc27d05232849b8643866585fd0a41c7af152950f8776f4dd5579e9853733f31461c6
@@ -7600,6 +8072,23 @@ __metadata:
languageName: node
linkType: hard
+"hardhat-docgen@npm:^1.2.1":
+ version: 1.2.1
+ resolution: "hardhat-docgen@npm:1.2.1"
+ dependencies:
+ css-loader: ^6.5.1
+ html-webpack-plugin: ^5.5.0
+ vue: ^2.6.14
+ vue-loader: ^15.9.8
+ vue-router: ^3.5.3
+ vue-template-compiler: ^2.6.14
+ webpack: ^5.65.0
+ peerDependencies:
+ hardhat: ^2.0.0
+ checksum: ea80335ecef6a3ebad23d8b09aa6c7fc15e75e77ab9dea164a04d7c91b58b44e572a5ee58c005882a1a52e10cb9f2cc87749a18521207ca3d050718f92bd34e1
+ languageName: node
+ linkType: hard
+
"hardhat-gas-reporter@npm:^1.0.4":
version: 1.0.4
resolution: "hardhat-gas-reporter@npm:1.0.4"
@@ -7815,6 +8304,13 @@ __metadata:
languageName: node
linkType: hard
+"hash-sum@npm:^1.0.2":
+ version: 1.0.2
+ resolution: "hash-sum@npm:1.0.2"
+ checksum: 268553ba6c84333f502481d101a7d65cd39f61963544f12fc3ce60264718f471796dbc37348cee08c5529f04fafeba041886a4d35721e34d6440a48a42629283
+ languageName: node
+ linkType: hard
+
"hash.js@npm:1.1.3":
version: 1.1.3
resolution: "hash.js@npm:1.1.3"
@@ -7835,7 +8331,7 @@ __metadata:
languageName: node
linkType: hard
-"he@npm:1.2.0":
+"he@npm:1.2.0, he@npm:^1.1.0, he@npm:^1.2.0":
version: 1.2.0
resolution: "he@npm:1.2.0"
bin:
@@ -7888,6 +8384,50 @@ __metadata:
languageName: node
linkType: hard
+"html-minifier-terser@npm:^6.0.2":
+ version: 6.1.0
+ resolution: "html-minifier-terser@npm:6.1.0"
+ dependencies:
+ camel-case: ^4.1.2
+ clean-css: ^5.2.2
+ commander: ^8.3.0
+ he: ^1.2.0
+ param-case: ^3.0.4
+ relateurl: ^0.2.7
+ terser: ^5.10.0
+ bin:
+ html-minifier-terser: cli.js
+ checksum: ac52c14006476f773204c198b64838477859dc2879490040efab8979c0207424da55d59df7348153f412efa45a0840a1ca3c757bf14767d23a15e3e389d37a93
+ languageName: node
+ linkType: hard
+
+"html-webpack-plugin@npm:^5.5.0":
+ version: 5.5.0
+ resolution: "html-webpack-plugin@npm:5.5.0"
+ dependencies:
+ "@types/html-minifier-terser": ^6.0.0
+ html-minifier-terser: ^6.0.2
+ lodash: ^4.17.21
+ pretty-error: ^4.0.0
+ tapable: ^2.0.0
+ peerDependencies:
+ webpack: ^5.20.0
+ checksum: f3d84d0df71fe2f5bac533cc74dce41ab058558cdcc6ff767d166a2abf1cf6fb8491d54d60ddbb34e95c00394e379ba52e0468e0284d1d0cc6a42987056e8219
+ languageName: node
+ linkType: hard
+
+"htmlparser2@npm:^6.1.0":
+ version: 6.1.0
+ resolution: "htmlparser2@npm:6.1.0"
+ dependencies:
+ domelementtype: ^2.0.1
+ domhandler: ^4.0.0
+ domutils: ^2.5.2
+ entities: ^2.0.0
+ checksum: 81a7b3d9c3bb9acb568a02fc9b1b81ffbfa55eae7f1c41ae0bf840006d1dbf54cb3aa245b2553e2c94db674840a9f0fdad7027c9a9d01a062065314039058c4e
+ languageName: node
+ linkType: hard
+
"http-basic@npm:^8.1.1":
version: 8.1.3
resolution: "http-basic@npm:8.1.3"
@@ -8024,6 +8564,15 @@ __metadata:
languageName: node
linkType: hard
+"icss-utils@npm:^5.0.0, icss-utils@npm:^5.1.0":
+ version: 5.1.0
+ resolution: "icss-utils@npm:5.1.0"
+ peerDependencies:
+ postcss: ^8.1.0
+ checksum: 5c324d283552b1269cfc13a503aaaa172a280f914e5b81544f3803bc6f06a3b585fb79f66f7c771a2c052db7982c18bf92d001e3b47282e3abbbb4c4cc488d68
+ languageName: node
+ linkType: hard
+
"idna-uts46-hx@npm:^2.3.1":
version: 2.3.1
resolution: "idna-uts46-hx@npm:2.3.1"
@@ -8775,6 +9324,17 @@ __metadata:
languageName: node
linkType: hard
+"jest-worker@npm:^27.4.1":
+ version: 27.4.5
+ resolution: "jest-worker@npm:27.4.5"
+ dependencies:
+ "@types/node": "*"
+ merge-stream: ^2.0.0
+ supports-color: ^8.0.0
+ checksum: eb0b6be412103299c3d8643ad26daf862826ca841bd2a3ff47d2d931804ab7d7f0db2fcdea7dbf47ce8eacb7742b3f2586c2d6ebdaa8d0ac77c65f7b698e7683
+ languageName: node
+ linkType: hard
+
"js-sha3@npm:0.5.7, js-sha3@npm:^0.5.7":
version: 0.5.7
resolution: "js-sha3@npm:0.5.7"
@@ -8879,7 +9439,7 @@ __metadata:
languageName: node
linkType: hard
-"json-parse-better-errors@npm:^1.0.1":
+"json-parse-better-errors@npm:^1.0.1, json-parse-better-errors@npm:^1.0.2":
version: 1.0.2
resolution: "json-parse-better-errors@npm:1.0.2"
checksum: ff2b5ba2a70e88fd97a3cb28c1840144c5ce8fae9cbeeddba15afa333a5c407cf0e42300cd0a2885dbb055227fe68d405070faad941beeffbfde9cf3b2c78c5d
@@ -9554,6 +10114,24 @@ __metadata:
languageName: node
linkType: hard
+"loader-runner@npm:^4.2.0":
+ version: 4.2.0
+ resolution: "loader-runner@npm:4.2.0"
+ checksum: e61aea8b6904b8af53d9de6f0484da86c462c0001f4511bedc837cec63deb9475cea813db62f702cd7930420ccb0e75c78112270ca5c8b61b374294f53c0cb3a
+ languageName: node
+ linkType: hard
+
+"loader-utils@npm:^1.0.2, loader-utils@npm:^1.1.0":
+ version: 1.4.0
+ resolution: "loader-utils@npm:1.4.0"
+ dependencies:
+ big.js: ^5.2.2
+ emojis-list: ^3.0.0
+ json5: ^1.0.1
+ checksum: d150b15e7a42ac47d935c8b484b79e44ff6ab4c75df7cc4cb9093350cf014ec0b17bdb60c5d6f91a37b8b218bd63b973e263c65944f58ca2573e402b9a27e717
+ languageName: node
+ linkType: hard
+
"locate-path@npm:^2.0.0":
version: 2.0.0
resolution: "locate-path@npm:2.0.0"
@@ -9697,6 +10275,15 @@ __metadata:
languageName: node
linkType: hard
+"lower-case@npm:^2.0.2":
+ version: 2.0.2
+ resolution: "lower-case@npm:2.0.2"
+ dependencies:
+ tslib: ^2.0.3
+ checksum: 83a0a5f159ad7614bee8bf976b96275f3954335a84fad2696927f609ddae902802c4f3312d86668722e668bef41400254807e1d3a7f2e8c3eede79691aa1f010
+ languageName: node
+ linkType: hard
+
"lowercase-keys@npm:^1.0.0, lowercase-keys@npm:^1.0.1":
version: 1.0.1
resolution: "lowercase-keys@npm:1.0.1"
@@ -9729,6 +10316,16 @@ __metadata:
languageName: node
linkType: hard
+"lru-cache@npm:^4.1.2":
+ version: 4.1.5
+ resolution: "lru-cache@npm:4.1.5"
+ dependencies:
+ pseudomap: ^1.0.2
+ yallist: ^2.1.2
+ checksum: 4bb4b58a36cd7dc4dcec74cbe6a8f766a38b7426f1ff59d4cf7d82a2aa9b9565cd1cb98f6ff60ce5cd174524868d7bc9b7b1c294371851356066ca9ac4cf135a
+ languageName: node
+ linkType: hard
+
"lru-cache@npm:^6.0.0":
version: 6.0.0
resolution: "lru-cache@npm:6.0.0"
@@ -9963,6 +10560,15 @@ __metadata:
languageName: node
linkType: hard
+"merge-source-map@npm:^1.1.0":
+ version: 1.1.0
+ resolution: "merge-source-map@npm:1.1.0"
+ dependencies:
+ source-map: ^0.6.1
+ checksum: 945a83dcc59eff77dde709be1d3d6cb575c11cd7164a7ccdc1c6f0d463aad7c12750a510bdf84af2c05fac4615c4305d97ac90477975348bb901a905c8e92c4b
+ languageName: node
+ linkType: hard
+
"merge-stream@npm:^2.0.0":
version: 2.0.0
resolution: "merge-stream@npm:2.0.0"
@@ -10080,7 +10686,7 @@ __metadata:
languageName: node
linkType: hard
-"mime-types@npm:^2.1.12, mime-types@npm:^2.1.16, mime-types@npm:~2.1.19, mime-types@npm:~2.1.24":
+"mime-types@npm:^2.1.12, mime-types@npm:^2.1.16, mime-types@npm:^2.1.27, mime-types@npm:~2.1.19, mime-types@npm:~2.1.24":
version: 2.1.34
resolution: "mime-types@npm:2.1.34"
dependencies:
@@ -10573,7 +11179,7 @@ __metadata:
languageName: node
linkType: hard
-"neo-async@npm:^2.6.0":
+"neo-async@npm:^2.6.0, neo-async@npm:^2.6.2":
version: 2.6.2
resolution: "neo-async@npm:2.6.2"
checksum: deac9f8d00eda7b2e5cd1b2549e26e10a0faa70adaa6fdadca701cc55f49ee9018e427f424bac0c790b7c7e2d3068db97f3093f1093975f2acb8f8818b936ed9
@@ -10594,6 +11200,16 @@ __metadata:
languageName: node
linkType: hard
+"no-case@npm:^3.0.4":
+ version: 3.0.4
+ resolution: "no-case@npm:3.0.4"
+ dependencies:
+ lower-case: ^2.0.2
+ tslib: ^2.0.3
+ checksum: 0b2ebc113dfcf737d48dde49cfebf3ad2d82a8c3188e7100c6f375e30eafbef9e9124aadc3becef237b042fd5eb0aad2fd78669c20972d045bbe7fea8ba0be5c
+ languageName: node
+ linkType: hard
+
"node-addon-api@npm:^2.0.0":
version: 2.0.2
resolution: "node-addon-api@npm:2.0.2"
@@ -10679,6 +11295,13 @@ __metadata:
languageName: node
linkType: hard
+"node-releases@npm:^2.0.1":
+ version: 2.0.1
+ resolution: "node-releases@npm:2.0.1"
+ checksum: b20dd8d4bced11f75060f0387e05e76b9dc4a0451f7bb3516eade6f50499ea7768ba95d8a60d520c193402df1e58cb3fe301510cc1c1ad68949c3d57b5149866
+ languageName: node
+ linkType: hard
+
"nofilter@npm:^1.0.4":
version: 1.0.4
resolution: "nofilter@npm:1.0.4"
@@ -10797,6 +11420,15 @@ __metadata:
languageName: node
linkType: hard
+"nth-check@npm:^2.0.1":
+ version: 2.0.1
+ resolution: "nth-check@npm:2.0.1"
+ dependencies:
+ boolbase: ^1.0.0
+ checksum: 5386d035c48438ff304fe687704d93886397349d1bed136de97aeae464caba10e8ffac55a04b215b86b3bc8897f33e0a5aa1045a9d8b2f251ae61b2a3ad3e450
+ languageName: node
+ linkType: hard
+
"number-is-nan@npm:^1.0.0":
version: 1.0.1
resolution: "number-is-nan@npm:1.0.1"
@@ -11197,6 +11829,16 @@ __metadata:
languageName: node
linkType: hard
+"param-case@npm:^3.0.4":
+ version: 3.0.4
+ resolution: "param-case@npm:3.0.4"
+ dependencies:
+ dot-case: ^3.0.4
+ tslib: ^2.0.3
+ checksum: b34227fd0f794e078776eb3aa6247442056cb47761e9cd2c4c881c86d84c64205f6a56ef0d70b41ee7d77da02c3f4ed2f88e3896a8fefe08bdfb4deca037c687
+ languageName: node
+ linkType: hard
+
"parent-module@npm:^1.0.0":
version: 1.0.1
resolution: "parent-module@npm:1.0.1"
@@ -11271,6 +11913,16 @@ __metadata:
languageName: node
linkType: hard
+"pascal-case@npm:^3.1.2":
+ version: 3.1.2
+ resolution: "pascal-case@npm:3.1.2"
+ dependencies:
+ no-case: ^3.0.4
+ tslib: ^2.0.3
+ checksum: ba98bfd595fc91ef3d30f4243b1aee2f6ec41c53b4546bfa3039487c367abaa182471dcfc830a1f9e1a0df00c14a370514fa2b3a1aacc68b15a460c31116873e
+ languageName: node
+ linkType: hard
+
"pascalcase@npm:^0.1.1":
version: 0.1.1
resolution: "pascalcase@npm:0.1.1"
@@ -11449,6 +12101,13 @@ __metadata:
languageName: node
linkType: hard
+"picocolors@npm:^0.2.1":
+ version: 0.2.1
+ resolution: "picocolors@npm:0.2.1"
+ checksum: 3b0f441f0062def0c0f39e87b898ae7461c3a16ffc9f974f320b44c799418cabff17780ee647fda42b856a1dc45897e2c62047e1b546d94d6d5c6962f45427b2
+ languageName: node
+ linkType: hard
+
"picocolors@npm:^1.0.0":
version: 1.0.0
resolution: "picocolors@npm:1.0.0"
@@ -11534,6 +12193,77 @@ __metadata:
languageName: node
linkType: hard
+"postcss-modules-extract-imports@npm:^3.0.0":
+ version: 3.0.0
+ resolution: "postcss-modules-extract-imports@npm:3.0.0"
+ peerDependencies:
+ postcss: ^8.1.0
+ checksum: 4b65f2f1382d89c4bc3c0a1bdc5942f52f3cb19c110c57bd591ffab3a5fee03fcf831604168205b0c1b631a3dce2255c70b61aaae3ef39d69cd7eb450c2552d2
+ languageName: node
+ linkType: hard
+
+"postcss-modules-local-by-default@npm:^4.0.0":
+ version: 4.0.0
+ resolution: "postcss-modules-local-by-default@npm:4.0.0"
+ dependencies:
+ icss-utils: ^5.0.0
+ postcss-selector-parser: ^6.0.2
+ postcss-value-parser: ^4.1.0
+ peerDependencies:
+ postcss: ^8.1.0
+ checksum: 6cf570badc7bc26c265e073f3ff9596b69bb954bc6ac9c5c1b8cba2995b80834226b60e0a3cbb87d5f399dbb52e6466bba8aa1d244f6218f99d834aec431a69d
+ languageName: node
+ linkType: hard
+
+"postcss-modules-scope@npm:^3.0.0":
+ version: 3.0.0
+ resolution: "postcss-modules-scope@npm:3.0.0"
+ dependencies:
+ postcss-selector-parser: ^6.0.4
+ peerDependencies:
+ postcss: ^8.1.0
+ checksum: 330b9398dbd44c992c92b0dc612c0626135e2cc840fee41841eb61247a6cfed95af2bd6f67ead9dd9d0bb41f5b0367129d93c6e434fa3e9c58ade391d9a5a138
+ languageName: node
+ linkType: hard
+
+"postcss-modules-values@npm:^4.0.0":
+ version: 4.0.0
+ resolution: "postcss-modules-values@npm:4.0.0"
+ dependencies:
+ icss-utils: ^5.0.0
+ peerDependencies:
+ postcss: ^8.1.0
+ checksum: f7f2cdf14a575b60e919ad5ea52fed48da46fe80db2733318d71d523fc87db66c835814940d7d05b5746b0426e44661c707f09bdb83592c16aea06e859409db6
+ languageName: node
+ linkType: hard
+
+"postcss-selector-parser@npm:^6.0.2, postcss-selector-parser@npm:^6.0.4":
+ version: 6.0.8
+ resolution: "postcss-selector-parser@npm:6.0.8"
+ dependencies:
+ cssesc: ^3.0.0
+ util-deprecate: ^1.0.2
+ checksum: 550351c8d04216106e259f7c433652aa6742dd7ddbedf7afdc313526963bb170589a6fefd1bc1fe6268a2cf9f5073d4ecb09bc7b5b4bef49edf80634300500af
+ languageName: node
+ linkType: hard
+
+"postcss-value-parser@npm:^4.1.0":
+ version: 4.2.0
+ resolution: "postcss-value-parser@npm:4.2.0"
+ checksum: 819ffab0c9d51cf0acbabf8996dffbfafbafa57afc0e4c98db88b67f2094cb44488758f06e5da95d7036f19556a4a732525e84289a425f4f6fd8e412a9d7442f
+ languageName: node
+ linkType: hard
+
+"postcss@npm:^7.0.36":
+ version: 7.0.39
+ resolution: "postcss@npm:7.0.39"
+ dependencies:
+ picocolors: ^0.2.1
+ source-map: ^0.6.1
+ checksum: 4ac793f506c23259189064bdc921260d869a115a82b5e713973c5af8e94fbb5721a5cc3e1e26840500d7e1f1fa42a209747c5b1a151918a9bc11f0d7ed9048e3
+ languageName: node
+ linkType: hard
+
"postcss@npm:^8.1.10":
version: 8.3.11
resolution: "postcss@npm:8.3.11"
@@ -11545,6 +12275,17 @@ __metadata:
languageName: node
linkType: hard
+"postcss@npm:^8.2.15":
+ version: 8.4.5
+ resolution: "postcss@npm:8.4.5"
+ dependencies:
+ nanoid: ^3.1.30
+ picocolors: ^1.0.0
+ source-map-js: ^1.0.1
+ checksum: b78abdd89c10f7b48f4bdcd376104a19d6e9c7495ab521729bdb3df315af6c211360e9f06887ad3bc0ab0f61a04b91d68ea11462997c79cced58b9ccd66fac07
+ languageName: node
+ linkType: hard
+
"postinstall-postinstall@npm:^2.1.0":
version: 2.1.0
resolution: "postinstall-postinstall@npm:2.1.0"
@@ -11621,6 +12362,15 @@ __metadata:
languageName: node
linkType: hard
+"prettier@npm:^1.18.2 || ^2.0.0":
+ version: 2.5.1
+ resolution: "prettier@npm:2.5.1"
+ bin:
+ prettier: bin-prettier.js
+ checksum: 21b9408476ea1c544b0e45d51ceb94a84789ff92095abb710942d780c862d0daebdb29972d47f6b4d0f7ebbfb0ffbf56cc2cfa3e3e9d1cca54864af185b15b66
+ languageName: node
+ linkType: hard
+
"prettier@npm:^2.1.2, prettier@npm:^2.4.1":
version: 2.4.1
resolution: "prettier@npm:2.4.1"
@@ -11630,6 +12380,16 @@ __metadata:
languageName: node
linkType: hard
+"pretty-error@npm:^4.0.0":
+ version: 4.0.0
+ resolution: "pretty-error@npm:4.0.0"
+ dependencies:
+ lodash: ^4.17.20
+ renderkid: ^3.0.0
+ checksum: a5b9137365690104ded6947dca2e33360bf55e62a4acd91b1b0d7baa3970e43754c628cc9e16eafbdd4e8f8bcb260a5865475d4fc17c3106ff2d61db4e72cdf3
+ languageName: node
+ linkType: hard
+
"printj@npm:~1.1.0":
version: 1.1.2
resolution: "printj@npm:1.1.2"
@@ -11720,7 +12480,7 @@ __metadata:
languageName: node
linkType: hard
-"pseudomap@npm:^1.0.1":
+"pseudomap@npm:^1.0.1, pseudomap@npm:^1.0.2":
version: 1.0.2
resolution: "pseudomap@npm:1.0.2"
checksum: 856c0aae0ff2ad60881168334448e898ad7a0e45fe7386d114b150084254c01e200c957cf378378025df4e052c7890c5bd933939b0e0d2ecfcc1dc2f0b2991f5
@@ -12211,6 +12971,26 @@ __metadata:
languageName: node
linkType: hard
+"relateurl@npm:^0.2.7":
+ version: 0.2.7
+ resolution: "relateurl@npm:0.2.7"
+ checksum: 5891e792eae1dfc3da91c6fda76d6c3de0333a60aa5ad848982ebb6dccaa06e86385fb1235a1582c680a3d445d31be01c6bfc0804ebbcab5aaf53fa856fde6b6
+ languageName: node
+ linkType: hard
+
+"renderkid@npm:^3.0.0":
+ version: 3.0.0
+ resolution: "renderkid@npm:3.0.0"
+ dependencies:
+ css-select: ^4.1.3
+ dom-converter: ^0.2.0
+ htmlparser2: ^6.1.0
+ lodash: ^4.17.21
+ strip-ansi: ^6.0.1
+ checksum: 77162b62d6f33ab81f337c39efce0439ff0d1f6d441e29c35183151f83041c7850774fb904da163d6c844264d440d10557714e6daa0b19e4561a5cd4ef305d41
+ languageName: node
+ linkType: hard
+
"repeat-element@npm:^1.1.2":
version: 1.1.4
resolution: "repeat-element@npm:1.1.4"
@@ -12663,6 +13443,17 @@ __metadata:
languageName: node
linkType: hard
+"schema-utils@npm:^3.1.0, schema-utils@npm:^3.1.1":
+ version: 3.1.1
+ resolution: "schema-utils@npm:3.1.1"
+ dependencies:
+ "@types/json-schema": ^7.0.8
+ ajv: ^6.12.5
+ ajv-keywords: ^3.5.2
+ checksum: fb73f3d759d43ba033c877628fe9751620a26879f6301d3dbeeb48cf2a65baec5cdf99da65d1bf3b4ff5444b2e59cbe4f81c2456b5e0d2ba7d7fd4aed5da29ce
+ languageName: node
+ linkType: hard
+
"scrypt-js@npm:2.0.4":
version: 2.0.4
resolution: "scrypt-js@npm:2.0.4"
@@ -12811,7 +13602,7 @@ __metadata:
languageName: node
linkType: hard
-"serialize-javascript@npm:6.0.0":
+"serialize-javascript@npm:6.0.0, serialize-javascript@npm:^6.0.0":
version: 6.0.0
resolution: "serialize-javascript@npm:6.0.0"
dependencies:
@@ -13255,6 +14046,13 @@ __metadata:
languageName: node
linkType: hard
+"source-map-js@npm:^1.0.1":
+ version: 1.0.1
+ resolution: "source-map-js@npm:1.0.1"
+ checksum: 22606113d62bbd468712b0cb0c46e9a8629de7eb081049c62a04d977a211abafd7d61455617f8b78daba0b6c0c7e7c88f8c6b5aaeacffac0a6676ecf5caac5ce
+ languageName: node
+ linkType: hard
+
"source-map-resolve@npm:^0.5.0":
version: 0.5.3
resolution: "source-map-resolve@npm:0.5.3"
@@ -13297,6 +14095,16 @@ __metadata:
languageName: node
linkType: hard
+"source-map-support@npm:~0.5.20":
+ version: 0.5.21
+ resolution: "source-map-support@npm:0.5.21"
+ dependencies:
+ buffer-from: ^1.0.0
+ source-map: ^0.6.0
+ checksum: 43e98d700d79af1d36f859bdb7318e601dfc918c7ba2e98456118ebc4c4872b327773e5a1df09b0524e9e5063bb18f0934538eace60cca2710d1fa687645d137
+ languageName: node
+ linkType: hard
+
"source-map-url@npm:^0.4.0":
version: 0.4.1
resolution: "source-map-url@npm:0.4.1"
@@ -13311,7 +14119,7 @@ __metadata:
languageName: node
linkType: hard
-"source-map@npm:^0.6.0, source-map@npm:^0.6.1":
+"source-map@npm:^0.6.0, source-map@npm:^0.6.1, source-map@npm:~0.6.0, source-map@npm:~0.6.1":
version: 0.6.1
resolution: "source-map@npm:0.6.1"
checksum: 59ce8640cf3f3124f64ac289012c2b8bd377c238e316fb323ea22fbfe83da07d81e000071d7242cad7a23cd91c7de98e4df8830ec3f133cb6133a5f6e9f67bc2
@@ -13327,6 +14135,13 @@ __metadata:
languageName: node
linkType: hard
+"source-map@npm:~0.7.2":
+ version: 0.7.3
+ resolution: "source-map@npm:0.7.3"
+ checksum: cd24efb3b8fa69b64bf28e3c1b1a500de77e84260c5b7f2b873f88284df17974157cc88d386ee9b6d081f08fdd8242f3fc05c953685a6ad81aad94c7393dedea
+ languageName: node
+ linkType: hard
+
"sourcemap-codec@npm:^1.4.4":
version: 1.4.8
resolution: "sourcemap-codec@npm:1.4.8"
@@ -13717,7 +14532,7 @@ __metadata:
languageName: node
linkType: hard
-"supports-color@npm:8.1.1":
+"supports-color@npm:8.1.1, supports-color@npm:^8.0.0":
version: 8.1.1
resolution: "supports-color@npm:8.1.1"
dependencies:
@@ -13824,6 +14639,13 @@ __metadata:
languageName: node
linkType: hard
+"tapable@npm:^2.0.0, tapable@npm:^2.1.1, tapable@npm:^2.2.0":
+ version: 2.2.1
+ resolution: "tapable@npm:2.2.1"
+ checksum: 3b7a1b4d86fa940aad46d9e73d1e8739335efd4c48322cb37d073eb6f80f5281889bf0320c6d8ffcfa1a0dd5bfdbd0f9d037e252ef972aca595330538aac4d51
+ languageName: node
+ linkType: hard
+
"tape@npm:^4.6.3":
version: 4.14.0
resolution: "tape@npm:4.14.0"
@@ -13895,6 +14717,46 @@ __metadata:
languageName: node
linkType: hard
+"terser-webpack-plugin@npm:^5.1.3":
+ version: 5.3.0
+ resolution: "terser-webpack-plugin@npm:5.3.0"
+ dependencies:
+ jest-worker: ^27.4.1
+ schema-utils: ^3.1.1
+ serialize-javascript: ^6.0.0
+ source-map: ^0.6.1
+ terser: ^5.7.2
+ peerDependencies:
+ webpack: ^5.1.0
+ peerDependenciesMeta:
+ "@swc/core":
+ optional: true
+ esbuild:
+ optional: true
+ uglify-js:
+ optional: true
+ checksum: f6735b8bb2604e8ca8b78d21f610fb2488866db72bb38e8d7c32aab97ea81fa0a19cabed074a431ff3dd9510d6efd505fc6930cdd8c1d3faa71c1bf7da4c7469
+ languageName: node
+ linkType: hard
+
+"terser@npm:^5.10.0, terser@npm:^5.7.2":
+ version: 5.10.0
+ resolution: "terser@npm:5.10.0"
+ dependencies:
+ commander: ^2.20.0
+ source-map: ~0.7.2
+ source-map-support: ~0.5.20
+ peerDependencies:
+ acorn: ^8.5.0
+ peerDependenciesMeta:
+ acorn:
+ optional: true
+ bin:
+ terser: bin/terser
+ checksum: 1080faeb6d5cd155bb39d9cc41d20a590eafc9869560d5285f255f6858604dcd135311e344188a106f87fedb12d096ad3799cfc2e65acd470b85d468b1c7bd4c
+ languageName: node
+ linkType: hard
+
"test-value@npm:^2.1.0":
version: 2.1.0
resolution: "test-value@npm:2.1.0"
@@ -14222,7 +15084,7 @@ __metadata:
languageName: node
linkType: hard
-"tslib@npm:^2":
+"tslib@npm:^2, tslib@npm:^2.0.3":
version: 2.3.1
resolution: "tslib@npm:2.3.1"
checksum: de17a98d4614481f7fcb5cd53ffc1aaf8654313be0291e1bfaee4b4bb31a20494b7d218ff2e15017883e8ea9626599b3b0e0229c18383ba9dce89da2adf15cb9
@@ -14665,7 +15527,7 @@ __metadata:
languageName: node
linkType: hard
-"util-deprecate@npm:^1.0.1, util-deprecate@npm:~1.0.1":
+"util-deprecate@npm:^1.0.1, util-deprecate@npm:^1.0.2, util-deprecate@npm:~1.0.1":
version: 1.0.2
resolution: "util-deprecate@npm:1.0.2"
checksum: 474acf1146cb2701fe3b074892217553dfcf9a031280919ba1b8d651a068c9b15d863b7303cb15bd00a862b498e6cf4ad7b4a08fb134edd5a6f7641681cb54a2
@@ -14699,6 +15561,13 @@ __metadata:
languageName: node
linkType: hard
+"utila@npm:~0.4":
+ version: 0.4.0
+ resolution: "utila@npm:0.4.0"
+ checksum: 97ffd3bd2bb80c773429d3fb8396469115cd190dded1e733f190d8b602bd0a1bcd6216b7ce3c4395ee3c79e3c879c19d268dbaae3093564cb169ad1212d436f4
+ languageName: node
+ linkType: hard
+
"utils-merge@npm:1.0.1":
version: 1.0.1
resolution: "utils-merge@npm:1.0.1"
@@ -14773,6 +15642,85 @@ __metadata:
languageName: node
linkType: hard
+"vue-hot-reload-api@npm:^2.3.0":
+ version: 2.3.4
+ resolution: "vue-hot-reload-api@npm:2.3.4"
+ checksum: 9befc0b3d6c1cc69430813fb7cfd2125c6a228730a36fad0653e4ddb60c8d4cf3ddc9649d2c9105c3d6044b42e8c8dce62b3c245bc65a6f187c1e2ca82a79252
+ languageName: node
+ linkType: hard
+
+"vue-loader@npm:^15.9.8":
+ version: 15.9.8
+ resolution: "vue-loader@npm:15.9.8"
+ dependencies:
+ "@vue/component-compiler-utils": ^3.1.0
+ hash-sum: ^1.0.2
+ loader-utils: ^1.1.0
+ vue-hot-reload-api: ^2.3.0
+ vue-style-loader: ^4.1.0
+ peerDependencies:
+ css-loader: "*"
+ webpack: ^3.0.0 || ^4.1.0 || ^5.0.0-0
+ peerDependenciesMeta:
+ cache-loader:
+ optional: true
+ vue-template-compiler:
+ optional: true
+ checksum: ca4c99b2617b207eb96925b889669f8bfecb6e82d22ed59220b324b6caaccc38bf3bc1d7961353155ab19ec71b791e887e8a06109ec719e8a791a2b00a2420bc
+ languageName: node
+ linkType: hard
+
+"vue-router@npm:^3.5.3":
+ version: 3.5.3
+ resolution: "vue-router@npm:3.5.3"
+ checksum: 7b2cc0d41ff2a8ec3da45761f29e14d0998119f47e4887f54e17eb534f7b4823acb778302891b0aa3a10c62ae8ba999393d7891cc08d36f02ea87aa268e2a36c
+ languageName: node
+ linkType: hard
+
+"vue-style-loader@npm:^4.1.0":
+ version: 4.1.3
+ resolution: "vue-style-loader@npm:4.1.3"
+ dependencies:
+ hash-sum: ^1.0.2
+ loader-utils: ^1.0.2
+ checksum: ef79d0c6329303d69c87f128f67e486bd37e9a8d416aa662edafae62fab727117b7452f50be8b11fe0c4cb43992344d5ef6a46b206f375fca4d37ae5a5b99185
+ languageName: node
+ linkType: hard
+
+"vue-template-compiler@npm:^2.6.14":
+ version: 2.6.14
+ resolution: "vue-template-compiler@npm:2.6.14"
+ dependencies:
+ de-indent: ^1.0.2
+ he: ^1.1.0
+ checksum: 0d03f804ac97e26629c78219929596cfd98f522e1f13b16dd42f13e3fff09b85fb8252ef3486e9d62ca7993f576386f587e760df0506230fa87141fdac8275ea
+ languageName: node
+ linkType: hard
+
+"vue-template-es2015-compiler@npm:^1.9.0":
+ version: 1.9.1
+ resolution: "vue-template-es2015-compiler@npm:1.9.1"
+ checksum: ad1e85662783be3ee262c323b05d12e6a5036fca24f16dc0f7ab92736b675919cb4fa4b79b28753eac73119b709d1b36789bf60e8ae423f50c4db35de9370e8b
+ languageName: node
+ linkType: hard
+
+"vue@npm:^2.6.14":
+ version: 2.6.14
+ resolution: "vue@npm:2.6.14"
+ checksum: 23524a1bdca094d62cb3491a46317eed75184b5d61d28fa846ea5d2b241c1cc7084fc67ee259d47a50a6d0bbc33ecaceb7bb52bff81312fe7da07263f3419942
+ languageName: node
+ linkType: hard
+
+"watchpack@npm:^2.3.1":
+ version: 2.3.1
+ resolution: "watchpack@npm:2.3.1"
+ dependencies:
+ glob-to-regexp: ^0.4.1
+ graceful-fs: ^4.1.2
+ checksum: 70a34f92842d94b5d842980f866d568d7a467de667c96ae5759c759f46587e49265863171f4650bdbafc5f3870a28f2b4453e9e847098ec4b718b38926d47d22
+ languageName: node
+ linkType: hard
+
"web3-bzz@npm:1.2.11":
version: 1.2.11
resolution: "web3-bzz@npm:1.2.11"
@@ -15375,6 +16323,50 @@ __metadata:
languageName: node
linkType: hard
+"webpack-sources@npm:^3.2.2":
+ version: 3.2.2
+ resolution: "webpack-sources@npm:3.2.2"
+ checksum: cc81f1f1bfd1c25c7a565598850294b515bcccf7974d0249b4a0c8c607307866ce3f9e8cdef1c74d5facfb0d993944c499cfd4b7c8f52d01359b6671cc5823d4
+ languageName: node
+ linkType: hard
+
+"webpack@npm:^5.65.0":
+ version: 5.65.0
+ resolution: "webpack@npm:5.65.0"
+ dependencies:
+ "@types/eslint-scope": ^3.7.0
+ "@types/estree": ^0.0.50
+ "@webassemblyjs/ast": 1.11.1
+ "@webassemblyjs/wasm-edit": 1.11.1
+ "@webassemblyjs/wasm-parser": 1.11.1
+ acorn: ^8.4.1
+ acorn-import-assertions: ^1.7.6
+ browserslist: ^4.14.5
+ chrome-trace-event: ^1.0.2
+ enhanced-resolve: ^5.8.3
+ es-module-lexer: ^0.9.0
+ eslint-scope: 5.1.1
+ events: ^3.2.0
+ glob-to-regexp: ^0.4.1
+ graceful-fs: ^4.2.4
+ json-parse-better-errors: ^1.0.2
+ loader-runner: ^4.2.0
+ mime-types: ^2.1.27
+ neo-async: ^2.6.2
+ schema-utils: ^3.1.0
+ tapable: ^2.1.1
+ terser-webpack-plugin: ^5.1.3
+ watchpack: ^2.3.1
+ webpack-sources: ^3.2.2
+ peerDependenciesMeta:
+ webpack-cli:
+ optional: true
+ bin:
+ webpack: bin/webpack.js
+ checksum: 221ab8ccd440cb678269e86689704bbef81cf41393eb266625873e30c6980ffaa055bb1a7d14bf9fc0f5a2e6f03d15d068cbb995bc876757c01a4ca27fd2870c
+ languageName: node
+ linkType: hard
+
"websocket@npm:1.0.32":
version: 1.0.32
resolution: "websocket@npm:1.0.32"
@@ -15736,6 +16728,13 @@ __metadata:
languageName: node
linkType: hard
+"yallist@npm:^2.1.2":
+ version: 2.1.2
+ resolution: "yallist@npm:2.1.2"
+ checksum: 9ba99409209f485b6fcb970330908a6d41fa1c933f75e08250316cce19383179a6b70a7e0721b89672ebb6199cc377bf3e432f55100da6a7d6e11902b0a642cb
+ languageName: node
+ linkType: hard
+
"yallist@npm:^3.0.0, yallist@npm:^3.0.2, yallist@npm:^3.1.1":
version: 3.1.1
resolution: "yallist@npm:3.1.1"
From 059bb753eb541db1ea4376724974e9025410a6ef Mon Sep 17 00:00:00 2001
From: jaybuidl
Date: Wed, 29 Dec 2021 19:52:14 +0000
Subject: [PATCH 09/14] refactor(DisputeKit): abstraction of the dispute kit
Renamed DisputeKit --> DisputeKitClassic because it implemented the
classic features of Kleros v1
Created DisputeKit as an abstraction so that KlerosCore is not concerned
with the specifics of each one.
---
.../src/arbitration/AbstractDisputeKit.sol | 29 -
contracts/src/arbitration/KlerosCore.sol | 2 +-
.../arbitration/dispute-kits/DisputeKit.sol | 422 +------------
.../dispute-kits/DisputeKitClassic.sol | 559 ++++++++++++++++++
contracts/test/arbitration/index.ts | 18 +-
5 files changed, 588 insertions(+), 442 deletions(-)
delete mode 100644 contracts/src/arbitration/AbstractDisputeKit.sol
create mode 100644 contracts/src/arbitration/dispute-kits/DisputeKitClassic.sol
diff --git a/contracts/src/arbitration/AbstractDisputeKit.sol b/contracts/src/arbitration/AbstractDisputeKit.sol
deleted file mode 100644
index 577b8003e..000000000
--- a/contracts/src/arbitration/AbstractDisputeKit.sol
+++ /dev/null
@@ -1,29 +0,0 @@
-// SPDX-License-Identifier: MIT
-
-pragma solidity ^0.8;
-
-import "./IArbitrator.sol";
-
-abstract contract AbstractDisputeKit {
- /**
- * Note: disputeID is maintained by Kleros Core, not the dispute kit
- * Note: the dispute kit does not receive any payment, Kleros Core does
- * Note: Permissioned
- */
- function createDispute(
- uint256 _disputeID,
- uint256 _arbitrationFee,
- uint256 _subcourtFeeForJuror,
- uint256 _subcourtMinStake,
- uint256 _subcourtAlpha,
- uint256 _choices,
- bytes calldata _extraData
- ) external virtual;
-
- /**
- * @dev Draws jurors for a dispute. Can be called in parts.
- * @param _disputeID The ID of the dispute.
- * @param _iterations The number of iterations to run.
- */
- function drawJurors(uint256 _disputeID, uint256 _iterations) external virtual;
-}
diff --git a/contracts/src/arbitration/KlerosCore.sol b/contracts/src/arbitration/KlerosCore.sol
index 3cc1d86d4..fc5436a15 100644
--- a/contracts/src/arbitration/KlerosCore.sol
+++ b/contracts/src/arbitration/KlerosCore.sol
@@ -17,7 +17,7 @@ import {SortitionSumTreeFactory} from "../data-structures/SortitionSumTreeFactor
/**
* @title KlerosCore
- * Core module contract for KlerosCourtV2.
+ * Core arbitrator contract for Kleros v2.
*/
contract KlerosCore is IArbitrator {
using SortitionSumTreeFactory for SortitionSumTreeFactory.SortitionSumTrees; // Use library functions for sortition sum trees.
diff --git a/contracts/src/arbitration/dispute-kits/DisputeKit.sol b/contracts/src/arbitration/dispute-kits/DisputeKit.sol
index cfbe887ae..1b5d8e736 100644
--- a/contracts/src/arbitration/dispute-kits/DisputeKit.sol
+++ b/contracts/src/arbitration/dispute-kits/DisputeKit.sol
@@ -10,138 +10,13 @@
pragma solidity ^0.8;
-import "../KlerosCore.sol";
-import "../../rng/RNG.sol";
-
-contract DisputeKit {
- // ************************************* //
- // * 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".
- }
-
- 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 => uint256) paidFees; // Tracks the fees paid for each choice in this round.
- mapping(uint256 => bool) hasPaid; // True if this choice was fully funded, false otherwise.
- mapping(address => mapping(uint256 => 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.
- }
-
- 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
- RNG public rng; // The random number generator
- 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.
-
- // ************************************* //
- // * Events * //
- // ************************************* //
-
- event Contribution(
- uint256 indexed _disputeID,
- uint256 indexed _round,
- uint256 _choice,
- address indexed _contributor,
- uint256 _amount
- );
-
- event Withdrawal(
- uint256 indexed _disputeID,
- uint256 indexed _round,
- uint256 _choice,
- address indexed _contributor,
- uint256 _amount
- );
-
- event ChoiceFunded(uint256 indexed _disputeID, uint256 indexed _round, uint256 indexed _choice);
-
- // ************************************* //
- // * Function Modifiers * //
- // ************************************* //
-
- modifier onlyByCore() {
- require(address(core) == msg.sender, "Access not allowed: KlerosCore only.");
- _;
- }
-
- modifier onlyByGovernor() {
- require(governor == msg.sender, "Access not allowed: Governor only.");
- _;
- }
-
- /** @dev Constructor.
- * @param _governor The governor's address.
- * @param _core The KlerosCore arbitrator.
- * @param _rng The random number generator.
- */
- constructor(
- address _governor,
- KlerosCore _core,
- RNG _rng
- ) {
- governor = _governor;
- core = _core;
- rng = _rng;
- }
-
- // ************************ //
- // * Governance * //
- // ************************ //
-
- /** @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 payable _core) external onlyByGovernor {
- core = KlerosCore(_core);
- }
+import "../IArbitrator.sol";
+/**
+ * @title DisputeKit
+ * Dispute kit abstraction for Kleros v2.
+ */
+abstract contract DisputeKit {
// ************************************* //
// * State Modifiers * //
// ************************************* //
@@ -156,56 +31,14 @@ contract DisputeKit {
uint256 _disputeID,
uint256 _numberOfChoices,
bytes calldata _extraData
- ) external onlyByCore {
- uint256 localDisputeID = disputes.length;
- Dispute storage dispute = disputes.push();
- dispute.numberOfChoices = _numberOfChoices;
-
- Round storage round = dispute.rounds.push();
- round.tied = true;
-
- coreDisputeIDToLocal[_disputeID] = localDisputeID;
- }
+ ) external virtual;
/** @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 _disputeID The ID of the dispute in Kleros Core.
* @return drawnAddress The drawn address.
*/
- function draw(uint256 _disputeID) external onlyByCore returns (address drawnAddress) {
- bytes32 key = bytes32(core.getSubcourtID(_disputeID)); // Get the ID of the tree.
- uint256 drawnNumber = getRandomNumber();
-
- (uint256 K, , uint256[] memory nodes) = core.getSortitionSumTree(key);
- uint256 treeIndex = 0;
- uint256 currentDrawnNumber = drawnNumber % nodes[0];
-
- Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]];
- Round storage round = dispute.rounds[dispute.rounds.length - 1];
-
- // TODO: Handle the situation when no one has staked yet.
- while (
- (K * treeIndex) + 1 < nodes.length // While it still has children.
- )
- for (uint256 i = 1; i <= K; i++) {
- // Loop over children.
- uint256 nodeIndex = (K * treeIndex) + i;
- uint256 nodeValue = nodes[nodeIndex];
-
- if (currentDrawnNumber >= nodeValue)
- currentDrawnNumber -= nodeValue; // Go to the next child.
- else {
- // Pick this child.
- treeIndex = nodeIndex;
- break;
- }
- }
-
- bytes32 ID = core.getSortitionSumTreeID(key, treeIndex);
- drawnAddress = stakePathIDToAccount(ID);
-
- round.votes.push(Vote({account: drawnAddress, commit: bytes32(0), choice: 0, voted: false}));
- }
+ function draw(uint256 _disputeID) external virtual returns (address drawnAddress);
/** @dev Sets the caller's commit for the specified votes.
* `O(n)` where
@@ -218,24 +51,7 @@ contract DisputeKit {
uint256 _disputeID,
uint256[] calldata _voteIDs,
bytes32 _commit
- ) external {
- require(
- core.getCurrentPeriod(_disputeID) == KlerosCore.Period.commit,
- "The dispute should be in Commit period."
- );
- require(_commit != bytes32(0), "Empty commit.");
-
- Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]];
- 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.");
- require(round.votes[_voteIDs[i]].commit == bytes32(0), "Already committed this vote.");
- round.votes[_voteIDs[i]].commit = _commit;
- }
- round.totalCommitted += _voteIDs.length;
-
- if (round.totalCommitted == round.votes.length) core.passPeriod(_disputeID);
- }
+ ) external virtual;
/** @dev Sets the caller's choices for the specified votes.
* `O(n)` where
@@ -250,107 +66,14 @@ contract DisputeKit {
uint256[] calldata _voteIDs,
uint256 _choice,
uint256 _salt
- ) external {
- require(core.getCurrentPeriod(_disputeID) == KlerosCore.Period.vote, "The dispute should be in Vote period.");
- require(_voteIDs.length > 0, "No voteID provided");
-
- Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]];
- require(_choice <= dispute.numberOfChoices, "Choice out of bounds");
-
- Round storage round = dispute.rounds[dispute.rounds.length - 1];
- bool hiddenVotes = core.areVotesHidden(core.getSubcourtID(_disputeID));
-
- // 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 subcourts 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;
- }
- }
-
- // Automatically switch period when voting is finished.
- if (round.totalVoted == round.votes.length) core.passPeriod(_disputeID);
- }
+ ) external virtual;
/** @dev Manages contributions, and appeals a dispute if at least two choices are fully funded.
* Note that the surplus deposit will be reimbursed.
* @param _disputeID Index of the dispute in Kleros Core contract.
* @param _choice A choice that receives funding.
*/
- function fundAppeal(uint256 _disputeID, uint256 _choice) external payable {
- Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]];
- require(_choice <= dispute.numberOfChoices, "There is no such ruling to fund.");
-
- (uint256 appealPeriodStart, uint256 appealPeriodEnd) = core.appealPeriod(_disputeID);
- require(block.timestamp >= appealPeriodStart && block.timestamp < appealPeriodEnd, "Appeal period is over.");
-
- uint256 multiplier;
- if (currentRuling(_disputeID) == _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];
- require(!round.hasPaid[_choice], "Appeal fee is already paid.");
- uint256 appealCost = core.appealCost(_disputeID);
- 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(_disputeID, dispute.rounds.length - 1, _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(_disputeID, dispute.rounds.length - 1, _choice);
- }
-
- if (round.fundedChoices.length > 1) {
- // At least two sides are fully funded.
- round.feeRewards = round.feeRewards - appealCost;
-
- Round storage newRound = dispute.rounds.push();
- newRound.tied = true;
- core.appeal{value: appealCost}(_disputeID);
- }
-
- if (msg.value > contribution) payable(msg.sender).send(msg.value - contribution);
- }
+ function fundAppeal(uint256 _disputeID, uint256 _choice) external payable virtual;
/** @dev Allows to withdraw any reimbursable fees or rewards after the dispute gets resolved.
* @param _disputeID Index of the dispute in Kleros Core contract.
@@ -364,37 +87,7 @@ contract DisputeKit {
address payable _beneficiary,
uint256 _round,
uint256 _choice
- ) external returns (uint256 amount) {
- require(core.isRuled(_disputeID), "Dispute should be resolved.");
-
- Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]];
- Round storage round = dispute.rounds[_round];
- uint256 finalRuling = currentRuling(_disputeID);
-
- 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(_disputeID, _round, _choice, _beneficiary, amount);
- }
- }
+ ) external virtual returns (uint256 amount);
// ************************************* //
// * Public Views * //
@@ -404,11 +97,7 @@ contract DisputeKit {
* @param _disputeID The ID of the dispute in Kleros Core.
* @return ruling The current ruling.
*/
- function currentRuling(uint256 _disputeID) public view returns (uint256 ruling) {
- Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]];
- Round storage round = dispute.rounds[dispute.rounds.length - 1];
- ruling = round.tied ? 0 : round.winningChoice;
- }
+ function currentRuling(uint256 _disputeID) public view virtual returns (uint256 ruling);
/** @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 _disputeID The ID of the dispute in Kleros Core.
@@ -420,38 +109,14 @@ contract DisputeKit {
uint256 _disputeID,
uint256 _round,
uint256 _voteID
- ) external view 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[_disputeID]];
- Round storage lastRound = dispute.rounds[dispute.rounds.length - 1];
- Vote storage vote = dispute.rounds[_round].votes[_voteID];
-
- if (vote.voted && (vote.choice == lastRound.winningChoice || lastRound.tied)) {
- return ONE_BASIS_POINT;
- } else {
- return 0;
- }
- }
+ ) external view virtual returns (uint256);
/** @dev Gets the number of jurors who are eligible to a reward in this round.
* @param _disputeID The ID of the dispute in Kleros Core.
* @param _round The ID of the round.
* @return The number of coherent jurors.
*/
- function getCoherentCount(uint256 _disputeID, uint256 _round) external view returns (uint256) {
- Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]];
- Round storage lastRound = dispute.rounds[dispute.rounds.length - 1];
- Round storage currentRound = dispute.rounds[_round];
- uint256 winningChoice = lastRound.winningChoice;
-
- if (currentRound.totalVoted == 0 || (!lastRound.tied && currentRound.counts[winningChoice] == 0)) {
- return 0;
- } else if (lastRound.tied) {
- return currentRound.totalVoted;
- } else {
- return currentRound.counts[winningChoice];
- }
- }
+ function getCoherentCount(uint256 _disputeID, uint256 _round) external view virtual returns (uint256);
/** @dev Returns true if the specified voter was active in this round.
* @param _disputeID The ID of the dispute in Kleros Core.
@@ -463,11 +128,7 @@ contract DisputeKit {
uint256 _disputeID,
uint256 _round,
uint256 _voteID
- ) external view returns (bool) {
- Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]];
- Vote storage vote = dispute.rounds[_round].votes[_voteID];
- return vote.voted;
- }
+ ) external view virtual returns (bool);
function getRoundInfo(
uint256 _disputeID,
@@ -476,6 +137,7 @@ contract DisputeKit {
)
external
view
+ virtual
returns (
uint256 winningChoice,
bool tied,
@@ -483,19 +145,7 @@ contract DisputeKit {
uint256 totalCommited,
uint256 nbVoters,
uint256 choiceCount
- )
- {
- Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]];
- Round storage round = dispute.rounds[_round];
- return (
- round.winningChoice,
- round.tied,
- round.totalVoted,
- round.totalCommitted,
- round.votes.length,
- round.counts[_choice]
);
- }
function getVoteInfo(
uint256 _disputeID,
@@ -504,45 +154,11 @@ contract DisputeKit {
)
external
view
+ virtual
returns (
address account,
bytes32 commit,
uint256 choice,
bool voted
- )
- {
- Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]];
- Vote storage vote = dispute.rounds[_round].votes[_voteID];
- return (vote.account, vote.commit, vote.choice, vote.voted);
- }
-
- // ************************************* //
- // * Internal/Private * //
- // ************************************* //
-
- /** @dev RNG function
- * @return rn A random number.
- */
- function getRandomNumber() private returns (uint256) {
- return rng.getUncorrelatedRN(block.number);
- }
-
- /** @dev Retrieves a juror's address from the stake path ID.
- * @param _stakePathID The stake path ID to unpack.
- * @return account The account.
- */
- function stakePathIDToAccount(bytes32 _stakePathID) private pure returns (address account) {
- assembly {
- // solium-disable-line security/no-inline-assembly
- let ptr := mload(0x40)
- for {
- let i := 0x00
- } lt(i, 0x14) {
- i := add(i, 0x01)
- } {
- mstore8(add(add(ptr, 0x0c), i), byte(i, _stakePathID))
- }
- account := mload(ptr)
- }
- }
+ );
}
diff --git a/contracts/src/arbitration/dispute-kits/DisputeKitClassic.sol b/contracts/src/arbitration/dispute-kits/DisputeKitClassic.sol
new file mode 100644
index 000000000..051e9f3bb
--- /dev/null
+++ b/contracts/src/arbitration/dispute-kits/DisputeKitClassic.sol
@@ -0,0 +1,559 @@
+// SPDX-License-Identifier: MIT
+
+/**
+ * @authors: [@unknownunknown1, @jaybuidl]
+ * @reviewers: []
+ * @auditors: []
+ * @bounties: []
+ * @deployments: []
+ */
+
+pragma solidity ^0.8;
+
+import "./DisputeKit.sol";
+import "../KlerosCore.sol";
+import "../../rng/RNG.sol";
+
+/**
+ * @title DisputeKitClassic
+ * Dispute kit implementation of the Kleros v1 features including:
+ * - Drawing system: proportional to staked PNK,
+ * - Vote aggreation system: plurality,
+ * - Incentive system: equal split between coherent votes,
+ * - Appeal system: fund 2 choices only, vote on any choice.
+ */
+contract DisputeKitClassic is DisputeKit {
+ // ************************************* //
+ // * 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".
+ }
+
+ 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 => uint256) paidFees; // Tracks the fees paid for each choice in this round.
+ mapping(uint256 => bool) hasPaid; // True if this choice was fully funded, false otherwise.
+ mapping(address => mapping(uint256 => 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.
+ }
+
+ 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
+ RNG public rng; // The random number generator
+ 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.
+
+ // ************************************* //
+ // * Events * //
+ // ************************************* //
+
+ event Contribution(
+ uint256 indexed _disputeID,
+ uint256 indexed _round,
+ uint256 _choice,
+ address indexed _contributor,
+ uint256 _amount
+ );
+
+ event Withdrawal(
+ uint256 indexed _disputeID,
+ uint256 indexed _round,
+ uint256 _choice,
+ address indexed _contributor,
+ uint256 _amount
+ );
+
+ event ChoiceFunded(uint256 indexed _disputeID, uint256 indexed _round, uint256 indexed _choice);
+
+ // ************************************* //
+ // * Function Modifiers * //
+ // ************************************* //
+
+ modifier onlyByCore() {
+ require(address(core) == msg.sender, "Access not allowed: KlerosCore only.");
+ _;
+ }
+
+ modifier onlyByGovernor() {
+ require(governor == msg.sender, "Access not allowed: Governor only.");
+ _;
+ }
+
+ /** @dev Constructor.
+ * @param _governor The governor's address.
+ * @param _core The KlerosCore arbitrator.
+ * @param _rng The random number generator.
+ */
+ constructor(
+ address _governor,
+ KlerosCore _core,
+ RNG _rng
+ ) {
+ governor = _governor;
+ core = _core;
+ rng = _rng;
+ }
+
+ // ************************ //
+ // * Governance * //
+ // ************************ //
+
+ /** @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 payable _core) external onlyByGovernor {
+ core = KlerosCore(_core);
+ }
+
+ // ************************************* //
+ // * 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 _disputeID 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.
+ */
+ function createDispute(
+ uint256 _disputeID,
+ uint256 _numberOfChoices,
+ bytes calldata _extraData
+ ) external override onlyByCore {
+ uint256 localDisputeID = disputes.length;
+ Dispute storage dispute = disputes.push();
+ dispute.numberOfChoices = _numberOfChoices;
+
+ Round storage round = dispute.rounds.push();
+ round.tied = true;
+
+ coreDisputeIDToLocal[_disputeID] = localDisputeID;
+ }
+
+ /** @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 _disputeID The ID of the dispute in Kleros Core.
+ * @return drawnAddress The drawn address.
+ */
+ function draw(uint256 _disputeID) external override onlyByCore returns (address drawnAddress) {
+ bytes32 key = bytes32(core.getSubcourtID(_disputeID)); // Get the ID of the tree.
+ uint256 drawnNumber = getRandomNumber();
+
+ (uint256 K, , uint256[] memory nodes) = core.getSortitionSumTree(key);
+ uint256 treeIndex = 0;
+ uint256 currentDrawnNumber = drawnNumber % nodes[0];
+
+ Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]];
+ Round storage round = dispute.rounds[dispute.rounds.length - 1];
+
+ // TODO: Handle the situation when no one has staked yet.
+ while (
+ (K * treeIndex) + 1 < nodes.length // While it still has children.
+ )
+ for (uint256 i = 1; i <= K; i++) {
+ // Loop over children.
+ uint256 nodeIndex = (K * treeIndex) + i;
+ uint256 nodeValue = nodes[nodeIndex];
+
+ if (currentDrawnNumber >= nodeValue)
+ currentDrawnNumber -= nodeValue; // Go to the next child.
+ else {
+ // Pick this child.
+ treeIndex = nodeIndex;
+ break;
+ }
+ }
+
+ bytes32 ID = core.getSortitionSumTreeID(key, treeIndex);
+ drawnAddress = stakePathIDToAccount(ID);
+
+ round.votes.push(Vote({account: drawnAddress, commit: bytes32(0), choice: 0, voted: false}));
+ }
+
+ /** @dev Sets the caller's commit for the specified votes.
+ * `O(n)` where
+ * `n` is the number of votes.
+ * @param _disputeID The ID of the dispute.
+ * @param _voteIDs The IDs of the votes.
+ * @param _commit The commit.
+ */
+ function castCommit(
+ uint256 _disputeID,
+ uint256[] calldata _voteIDs,
+ bytes32 _commit
+ ) external override {
+ require(
+ core.getCurrentPeriod(_disputeID) == KlerosCore.Period.commit,
+ "The dispute should be in Commit period."
+ );
+ require(_commit != bytes32(0), "Empty commit.");
+
+ Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]];
+ 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.");
+ require(round.votes[_voteIDs[i]].commit == bytes32(0), "Already committed this vote.");
+ round.votes[_voteIDs[i]].commit = _commit;
+ }
+ round.totalCommitted += _voteIDs.length;
+
+ if (round.totalCommitted == round.votes.length) core.passPeriod(_disputeID);
+ }
+
+ /** @dev Sets the caller's choices for the specified votes.
+ * `O(n)` where
+ * `n` is the number of votes.
+ * @param _disputeID The ID of the dispute.
+ * @param _voteIDs The IDs of the votes.
+ * @param _choice The choice.
+ * @param _salt The salt for the commit if the votes were hidden.
+ */
+ function castVote(
+ uint256 _disputeID,
+ uint256[] calldata _voteIDs,
+ uint256 _choice,
+ uint256 _salt
+ ) external override {
+ require(core.getCurrentPeriod(_disputeID) == KlerosCore.Period.vote, "The dispute should be in Vote period.");
+ require(_voteIDs.length > 0, "No voteID provided");
+
+ Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]];
+ require(_choice <= dispute.numberOfChoices, "Choice out of bounds");
+
+ Round storage round = dispute.rounds[dispute.rounds.length - 1];
+ bool hiddenVotes = core.areVotesHidden(core.getSubcourtID(_disputeID));
+
+ // 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 subcourts 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;
+ }
+ }
+
+ // Automatically switch period when voting is finished.
+ if (round.totalVoted == round.votes.length) core.passPeriod(_disputeID);
+ }
+
+ /** @dev Manages contributions, and appeals a dispute if at least two choices are fully funded.
+ * Note that the surplus deposit will be reimbursed.
+ * @param _disputeID Index of the dispute in Kleros Core contract.
+ * @param _choice A choice that receives funding.
+ */
+ function fundAppeal(uint256 _disputeID, uint256 _choice) external payable override {
+ Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]];
+ require(_choice <= dispute.numberOfChoices, "There is no such ruling to fund.");
+
+ (uint256 appealPeriodStart, uint256 appealPeriodEnd) = core.appealPeriod(_disputeID);
+ require(block.timestamp >= appealPeriodStart && block.timestamp < appealPeriodEnd, "Appeal period is over.");
+
+ uint256 multiplier;
+ if (currentRuling(_disputeID) == _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];
+ require(!round.hasPaid[_choice], "Appeal fee is already paid.");
+ uint256 appealCost = core.appealCost(_disputeID);
+ 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(_disputeID, dispute.rounds.length - 1, _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(_disputeID, dispute.rounds.length - 1, _choice);
+ }
+
+ if (round.fundedChoices.length > 1) {
+ // At least two sides are fully funded.
+ round.feeRewards = round.feeRewards - appealCost;
+
+ Round storage newRound = dispute.rounds.push();
+ newRound.tied = true;
+ core.appeal{value: appealCost}(_disputeID);
+ }
+
+ if (msg.value > contribution) payable(msg.sender).send(msg.value - contribution);
+ }
+
+ /** @dev Allows to withdraw any reimbursable fees or rewards after the dispute gets resolved.
+ * @param _disputeID Index of the dispute in Kleros Core contract.
+ * @param _beneficiary The address whose rewards to withdraw.
+ * @param _round The round 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 _disputeID,
+ address payable _beneficiary,
+ uint256 _round,
+ uint256 _choice
+ ) external override returns (uint256 amount) {
+ require(core.isRuled(_disputeID), "Dispute should be resolved.");
+
+ Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]];
+ Round storage round = dispute.rounds[_round];
+ uint256 finalRuling = currentRuling(_disputeID);
+
+ 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(_disputeID, _round, _choice, _beneficiary, amount);
+ }
+ }
+
+ // ************************************* //
+ // * Public Views * //
+ // ************************************* //
+
+ /** @dev Gets the current ruling of a specified dispute.
+ * @param _disputeID The ID of the dispute in Kleros Core.
+ * @return ruling The current ruling.
+ */
+ function currentRuling(uint256 _disputeID) public view override returns (uint256 ruling) {
+ Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]];
+ Round storage round = dispute.rounds[dispute.rounds.length - 1];
+ ruling = round.tied ? 0 : round.winningChoice;
+ }
+
+ /** @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 _disputeID The ID of the dispute in Kleros Core.
+ * @param _round The ID of the round.
+ * @param _voteID The ID of the vote.
+ * @return The degree of coherence in basis points.
+ */
+ function getDegreeOfCoherence(
+ uint256 _disputeID,
+ uint256 _round,
+ uint256 _voteID
+ ) 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[_disputeID]];
+ Round storage lastRound = dispute.rounds[dispute.rounds.length - 1];
+ Vote storage vote = dispute.rounds[_round].votes[_voteID];
+
+ if (vote.voted && (vote.choice == lastRound.winningChoice || lastRound.tied)) {
+ return ONE_BASIS_POINT;
+ } else {
+ return 0;
+ }
+ }
+
+ /** @dev Gets the number of jurors who are eligible to a reward in this round.
+ * @param _disputeID The ID of the dispute in Kleros Core.
+ * @param _round The ID of the round.
+ * @return The number of coherent jurors.
+ */
+ function getCoherentCount(uint256 _disputeID, uint256 _round) external view override returns (uint256) {
+ Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]];
+ Round storage lastRound = dispute.rounds[dispute.rounds.length - 1];
+ Round storage currentRound = dispute.rounds[_round];
+ uint256 winningChoice = lastRound.winningChoice;
+
+ if (currentRound.totalVoted == 0 || (!lastRound.tied && currentRound.counts[winningChoice] == 0)) {
+ return 0;
+ } else if (lastRound.tied) {
+ return currentRound.totalVoted;
+ } else {
+ return currentRound.counts[winningChoice];
+ }
+ }
+
+ /** @dev Returns true if the specified voter was active in this round.
+ * @param _disputeID The ID of the dispute in Kleros Core.
+ * @param _round The ID of the round.
+ * @param _voteID The ID of the voter.
+ * @return Whether the voter was active or not.
+ */
+ function isVoteActive(
+ uint256 _disputeID,
+ uint256 _round,
+ uint256 _voteID
+ ) external view override returns (bool) {
+ Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]];
+ Vote storage vote = dispute.rounds[_round].votes[_voteID];
+ return vote.voted;
+ }
+
+ function getRoundInfo(
+ uint256 _disputeID,
+ uint256 _round,
+ uint256 _choice
+ )
+ external
+ view
+ override
+ returns (
+ uint256 winningChoice,
+ bool tied,
+ uint256 totalVoted,
+ uint256 totalCommited,
+ uint256 nbVoters,
+ uint256 choiceCount
+ )
+ {
+ Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]];
+ Round storage round = dispute.rounds[_round];
+ return (
+ round.winningChoice,
+ round.tied,
+ round.totalVoted,
+ round.totalCommitted,
+ round.votes.length,
+ round.counts[_choice]
+ );
+ }
+
+ function getVoteInfo(
+ uint256 _disputeID,
+ uint256 _round,
+ uint256 _voteID
+ )
+ external
+ view
+ override
+ returns (
+ address account,
+ bytes32 commit,
+ uint256 choice,
+ bool voted
+ )
+ {
+ Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]];
+ Vote storage vote = dispute.rounds[_round].votes[_voteID];
+ return (vote.account, vote.commit, vote.choice, vote.voted);
+ }
+
+ // ************************************* //
+ // * Internal/Private * //
+ // ************************************* //
+
+ /** @dev RNG function
+ * @return rn A random number.
+ */
+ function getRandomNumber() private returns (uint256) {
+ return rng.getUncorrelatedRN(block.number);
+ }
+
+ /** @dev Retrieves a juror's address from the stake path ID.
+ * @param _stakePathID The stake path ID to unpack.
+ * @return account The account.
+ */
+ function stakePathIDToAccount(bytes32 _stakePathID) private pure returns (address account) {
+ assembly {
+ // solium-disable-line security/no-inline-assembly
+ let ptr := mload(0x40)
+ for {
+ let i := 0x00
+ } lt(i, 0x14) {
+ i := add(i, 0x01)
+ } {
+ mstore8(add(add(ptr, 0x0c), i), byte(i, _stakePathID))
+ }
+ account := mload(ptr)
+ }
+ }
+}
diff --git a/contracts/test/arbitration/index.ts b/contracts/test/arbitration/index.ts
index adde4eced..f00e76008 100644
--- a/contracts/test/arbitration/index.ts
+++ b/contracts/test/arbitration/index.ts
@@ -7,7 +7,7 @@ const WINNER_STAKE_MULTIPLIER = 3000;
const LOSER_STAKE_MULTIPLIER = 7000;
const MULTIPLIER_DENOMINATOR = 10000;
-describe("DisputeKitPlurality", function () {
+describe("DisputeKitClassic", function () {
// eslint-disable-next-line no-unused-vars
let deployer, claimant, supporter, challenger, innocentBystander;
let core, disputeKit, arbitrable;
@@ -35,28 +35,28 @@ describe("DisputeKitPlurality", function () {
});
async function deployContracts(deployer) {
- const ConstantNGFactory = await ethers.getContractFactory("ConstantNG", deployer);
- const rng = await ConstantNGFactory.deploy(42);
+ const constantNGFactory = await ethers.getContractFactory("ConstantNG", deployer);
+ const rng = await constantNGFactory.deploy(42);
await rng.deployed();
- const disputeKitFactory = await ethers.getContractFactory("DisputeKit", deployer);
+ const disputeKitFactory = await ethers.getContractFactory("DisputeKitClassic", deployer);
const disputeKit = await disputeKitFactory.deploy(
deployer.address,
- ethers.constants.AddressZero,
+ ethers.constants.AddressZero, // KlerosCore is set later once it is deployed
rng.address
);
await disputeKit.deployed();
- const SortitionSumTreeLibraryFactory = await ethers.getContractFactory("SortitionSumTreeFactory", deployer);
- const library = await SortitionSumTreeLibraryFactory.deploy();
+ const sortitionSumTreeLibraryFactory = await ethers.getContractFactory("SortitionSumTreeFactory", deployer);
+ const library = await sortitionSumTreeLibraryFactory.deploy();
- const KlerosCoreFactory = await ethers.getContractFactory("KlerosCore", {
+ const klerosCoreFactory = await ethers.getContractFactory("KlerosCore", {
signer: deployer,
libraries: {
SortitionSumTreeFactory: library.address,
},
});
- const core = await KlerosCoreFactory.deploy(
+ const core = await klerosCoreFactory.deploy(
deployer.address,
ethers.constants.AddressZero, // should be an ERC20
ethers.constants.AddressZero, // should be a Juror Prosecution module
From a26c0229eb9856f440001d75c0bcaece2cdb351a Mon Sep 17 00:00:00 2001
From: jaybuidl
Date: Thu, 30 Dec 2021 01:50:45 +0000
Subject: [PATCH 10/14] refactor(KlerosCore): extracted a Round struct from the
Dispute struct
---
contracts/src/arbitration/KlerosCore.sol | 136 +++++++++---------
.../dispute-kits/DisputeKitClassic.sol | 16 +--
2 files changed, 73 insertions(+), 79 deletions(-)
diff --git a/contracts/src/arbitration/KlerosCore.sol b/contracts/src/arbitration/KlerosCore.sol
index fc5436a15..cad626e1c 100644
--- a/contracts/src/arbitration/KlerosCore.sol
+++ b/contracts/src/arbitration/KlerosCore.sol
@@ -22,7 +22,9 @@ import {SortitionSumTreeFactory} from "../data-structures/SortitionSumTreeFactor
contract KlerosCore is IArbitrator {
using SortitionSumTreeFactory for SortitionSumTreeFactory.SortitionSumTrees; // Use library functions for sortition sum trees.
- /* Enums */
+ // ************************************* //
+ // * Enums / Structs * //
+ // ************************************* //
enum Period {
evidence, // Evidence can be submitted. This is also when drawing has to take place.
@@ -32,8 +34,6 @@ contract KlerosCore is IArbitrator {
execution // Tokens are redistributed and the ruling is executed.
}
- /* Structs */
-
struct Court {
uint96 parent; // The parent court.
bool hiddenVotes; // Whether to use commit and reveal or not.
@@ -56,10 +56,14 @@ contract KlerosCore is IArbitrator {
uint256 lastPeriodChange; // The last time the period was changed.
uint256 nbVotes; // The total number of votes the dispute can possibly have in the current round. Former votes[_appeal].length.
mapping(uint256 => address[]) drawnJurors; // Addresses of the drawn jurors in the form `drawnJurors[_appeal]`.
- uint256[] tokensAtStakePerJuror; // The amount of tokens at stake for each juror in the form `tokensAtStakePerJuror[appeal]`.
- uint256[] totalFeesForJurors; // The total juror fees paid in the form `totalFeesForJurors[appeal]`.
- uint256[] repartitionsInEachRound; // A counter of vote reward repartitions made in each round in the form `repartitionsInEachRound[appeal]`.
- uint256[] penaltiesInEachRound; // The amount of tokens collected from penalties in each round in the form `penaltiesInEachRound[appeal]`.
+ Round[] rounds;
+ }
+
+ struct Round {
+ uint256 tokensAtStakePerJuror; // The amount of tokens at stake for each juror in the form `tokensAtStakePerJuror[appeal]`.
+ uint256 totalFeesForJurors; // The total juror fees paid in the form `totalFeesForJurors[appeal]`.
+ uint256 repartitions; // A counter of vote reward repartitions made in each round in the form `repartitionsInEachRound[appeal]`.
+ uint256 penalties; // The amount of tokens collected from penalties in each round in the form `penaltiesInEachRound[appeal]`.
}
struct Juror {
@@ -69,15 +73,15 @@ contract KlerosCore is IArbitrator {
mapping(uint96 => uint256) lockedTokens; // The number of tokens the juror has locked in the subcourt in the form `lockedTokens[subcourtID]`.
}
- /* Constants */
+ // ************************************* //
+ // * Storage * //
+ // ************************************* //
uint256 public constant MAX_STAKE_PATHS = 4; // The maximum number of stake paths a juror can have.
uint256 public constant MIN_JURORS = 3; // The global default minimum number of jurors in a dispute.
uint256 public constant ALPHA_DIVISOR = 1e4; // The number to divide `Court.alpha` by.
uint256 public constant NON_PAYABLE_AMOUNT = (2**256 - 2) / 2; // An amount higher than the supply of ETH.
- /* Storage */
-
address public governor; // The governor of the contract.
IERC20 public pinakion; // The Pinakion token contract.
// TODO: interactions with jurorProsecutionModule.
@@ -92,7 +96,9 @@ contract KlerosCore is IArbitrator {
mapping(address => Juror) internal jurors; // The jurors.
SortitionSumTreeFactory.SortitionSumTrees internal sortitionSumTrees; // The sortition sum trees.
- /* Events */
+ // ************************************* //
+ // * Events * //
+ // ************************************* //
event StakeSet(address indexed _address, uint256 _subcourtID, uint256 _amount, uint256 _newTotalStake);
event NewPeriod(uint256 indexed _disputeID, Period _period);
@@ -106,7 +112,9 @@ contract KlerosCore is IArbitrator {
int256 _ETHAmount
);
- /* Modifiers */
+ // ************************************* //
+ // * Function Modifiers * //
+ // ************************************* //
modifier onlyByGovernor() {
require(governor == msg.sender, "Access not allowed: Governor only.");
@@ -138,7 +146,7 @@ contract KlerosCore is IArbitrator {
uint256 _jurorsForCourtJump,
uint256[4] memory _timesPerPeriod,
uint256 _sortitionSumTreeK
- ) public {
+ ) {
governor = _governor;
pinakion = _pinakion;
jurorProsecutionModule = _jurorProsecutionModule;
@@ -161,8 +169,6 @@ contract KlerosCore is IArbitrator {
sortitionSumTrees.createTree(bytes32(0), _sortitionSumTreeK);
}
- /* External and public */
-
// ************************ //
// * Governance * //
// ************************ //
@@ -331,16 +337,16 @@ contract KlerosCore is IArbitrator {
}
}
- // ************************** //
- // * General flow * //
- // ************************** //
+ // ************************************* //
+ // * State Modifiers * //
+ // ************************************* //
/** @dev Sets the caller's stake in a subcourt.
* @param _subcourtID The ID of the subcourt.
* @param _stake The new stake.
*/
function setStake(uint96 _subcourtID, uint256 _stake) external {
- _setStake(msg.sender, _subcourtID, _stake);
+ setStakeForAccount(msg.sender, _subcourtID, _stake);
}
/** @dev Creates a dispute. Must be called by the arbitrable contract.
@@ -375,12 +381,11 @@ contract KlerosCore is IArbitrator {
dispute.lastPeriodChange = block.timestamp;
dispute.nbVotes = msg.value / courts[dispute.subcourtID].feeForJuror;
- dispute.tokensAtStakePerJuror.push(
- (courts[dispute.subcourtID].minStake * courts[dispute.subcourtID].alpha) / ALPHA_DIVISOR
- );
- dispute.totalFeesForJurors.push(msg.value);
- dispute.repartitionsInEachRound.push(0);
- dispute.penaltiesInEachRound.push(0);
+ Round storage round = dispute.rounds.push();
+ round.tokensAtStakePerJuror =
+ (courts[dispute.subcourtID].minStake * courts[dispute.subcourtID].alpha) /
+ ALPHA_DIVISOR;
+ round.totalFeesForJurors = msg.value;
disputeKit.createDispute(disputeID, _numberOfChoices, _extraData);
emit DisputeCreation(disputeID, IArbitrable(msg.sender));
@@ -452,9 +457,9 @@ contract KlerosCore is IArbitrator {
address drawnAddress = disputeKit.draw(_disputeID);
if (drawnAddress != address(0)) {
// In case no one has staked at the court yet.
- jurors[drawnAddress].lockedTokens[dispute.subcourtID] += dispute.tokensAtStakePerJuror[
- dispute.currentRound
- ];
+ jurors[drawnAddress].lockedTokens[dispute.subcourtID] += dispute
+ .rounds[dispute.currentRound]
+ .tokensAtStakePerJuror;
dispute.drawnJurors[dispute.currentRound].push(drawnAddress);
emit Draw(drawnAddress, _disputeID, dispute.currentRound, i);
}
@@ -462,14 +467,15 @@ contract KlerosCore is IArbitrator {
}
/** @dev Appeals the ruling of a specified dispute.
+ * Note: Access restricted to the Dispute Kit for this `disputeID`.
* @param _disputeID The ID of the dispute.
*/
function appeal(uint256 _disputeID) external payable {
require(msg.value >= appealCost(_disputeID), "Not enough ETH to cover appeal cost.");
Dispute storage dispute = disputes[_disputeID];
- require(dispute.period == Period.appeal, "Dispute is not appealable");
- require(msg.sender == address(dispute.disputeKit), "Can only be called by the dispute kit.");
+ require(dispute.period == Period.appeal, "Dispute is not appealable.");
+ require(msg.sender == address(dispute.disputeKit), "Access not allowed: Dispute Kit only.");
if (dispute.nbVotes >= courts[dispute.subcourtID].jurorsForCourtJump)
// Jump to parent subcourt.
@@ -481,12 +487,11 @@ contract KlerosCore is IArbitrator {
// As many votes that can be afforded by the provided funds.
dispute.nbVotes = msg.value / courts[dispute.subcourtID].feeForJuror;
- dispute.tokensAtStakePerJuror.push(
- (courts[dispute.subcourtID].minStake * courts[dispute.subcourtID].alpha) / ALPHA_DIVISOR
- );
- dispute.totalFeesForJurors.push(msg.value);
- dispute.repartitionsInEachRound.push(0);
- dispute.penaltiesInEachRound.push(0);
+ Round storage extraRound = dispute.rounds.push();
+ extraRound.tokensAtStakePerJuror =
+ (courts[dispute.subcourtID].minStake * courts[dispute.subcourtID].alpha) /
+ ALPHA_DIVISOR;
+ extraRound.totalFeesForJurors = msg.value;
dispute.currentRound++;
@@ -507,8 +512,8 @@ contract KlerosCore is IArbitrator {
Dispute storage dispute = disputes[_disputeID];
require(dispute.period == Period.execution, "Should be execution period.");
- uint256 end = dispute.repartitionsInEachRound[_appeal] + _iterations;
- uint256 penaltiesInRoundCache = dispute.penaltiesInEachRound[_appeal]; // For saving gas.
+ uint256 end = dispute.rounds[_appeal].repartitions + _iterations;
+ uint256 penaltiesInRoundCache = dispute.rounds[_appeal].penalties; // For saving gas.
uint256 numberOfVotesInRound = dispute.drawnJurors[_appeal].length;
uint256 coherentCount = dispute.disputeKit.getCoherentCount(_disputeID, _appeal); // Total number of jurors that are eligible to a reward in this round.
@@ -524,14 +529,14 @@ contract KlerosCore is IArbitrator {
if (end > numberOfVotesInRound * 2) end = numberOfVotesInRound * 2;
}
- for (uint256 i = dispute.repartitionsInEachRound[_appeal]; i < end; i++) {
+ for (uint256 i = dispute.rounds[_appeal].repartitions; i < end; i++) {
// Penalty.
if (i < numberOfVotesInRound) {
degreeOfCoherence = dispute.disputeKit.getDegreeOfCoherence(_disputeID, _appeal, i);
if (degreeOfCoherence > ALPHA_DIVISOR) degreeOfCoherence = ALPHA_DIVISOR; // Make sure the degree doesn't exceed 1, though it should be ensured by the dispute kit.
- uint256 penalty = (dispute.tokensAtStakePerJuror[_appeal] * (ALPHA_DIVISOR - degreeOfCoherence)) /
- ALPHA_DIVISOR; // Fully coherent jurors won't be penalized.
+ uint256 penalty = (dispute.rounds[_appeal].tokensAtStakePerJuror *
+ (ALPHA_DIVISOR - degreeOfCoherence)) / ALPHA_DIVISOR; // Fully coherent jurors won't be penalized.
penaltiesInRoundCache += penalty;
account = dispute.drawnJurors[_appeal][i];
@@ -541,14 +546,14 @@ contract KlerosCore is IArbitrator {
// Unstake the juror if he lost due to inactivity.
if (!dispute.disputeKit.isVoteActive(_disputeID, _appeal, i)) {
for (uint256 j = 0; j < jurors[account].subcourtIDs.length; j++)
- _setStake(account, jurors[account].subcourtIDs[j], 0);
+ setStakeForAccount(account, jurors[account].subcourtIDs[j], 0);
}
emit TokenAndETHShift(account, _disputeID, -int256(penalty), 0);
if (i == numberOfVotesInRound - 1) {
if (coherentCount == 0) {
// No one was coherent. Send the rewards to governor.
- payable(governor).send(dispute.totalFeesForJurors[_appeal]);
+ payable(governor).send(dispute.rounds[_appeal].totalFeesForJurors);
pinakion.transfer(governor, penaltiesInRoundCache);
}
}
@@ -563,12 +568,12 @@ contract KlerosCore is IArbitrator {
account = dispute.drawnJurors[_appeal][i % 2];
// Release the rest of the tokens of the juror for this round.
jurors[account].lockedTokens[dispute.subcourtID] -=
- (dispute.tokensAtStakePerJuror[_appeal] * degreeOfCoherence) /
+ (dispute.rounds[_appeal].tokensAtStakePerJuror * degreeOfCoherence) /
ALPHA_DIVISOR;
// TODO: properly update staked tokens in case of reward.
uint256 tokenReward = ((penaltiesInRoundCache / coherentCount) * degreeOfCoherence) / ALPHA_DIVISOR;
- uint256 ETHReward = ((dispute.totalFeesForJurors[_appeal] / coherentCount) * degreeOfCoherence) /
+ uint256 ETHReward = ((dispute.rounds[_appeal].totalFeesForJurors / coherentCount) * degreeOfCoherence) /
ALPHA_DIVISOR;
pinakion.transfer(account, tokenReward);
@@ -577,9 +582,9 @@ contract KlerosCore is IArbitrator {
}
}
- if (dispute.penaltiesInEachRound[_appeal] != penaltiesInRoundCache)
- dispute.penaltiesInEachRound[_appeal] = penaltiesInRoundCache;
- dispute.repartitionsInEachRound[_appeal] = end;
+ if (dispute.rounds[_appeal].penalties != penaltiesInRoundCache)
+ dispute.rounds[_appeal].penalties = penaltiesInRoundCache;
+ dispute.rounds[_appeal].repartitions = end;
}
/** @dev Executes a specified dispute's ruling. UNTRUSTED.
@@ -595,9 +600,9 @@ contract KlerosCore is IArbitrator {
dispute.arbitrated.rule(_disputeID, winningChoice);
}
- // ********************* //
- // * Getters * //
- // ********************* //
+ // ************************************* //
+ // * Public Views * //
+ // ************************************* //
/** @dev Gets the cost of arbitration in a specified subcourt.
* @param _extraData Additional info about the dispute. We use it to pass the ID of the subcourt to create the dispute in (first 32 bytes)
@@ -651,8 +656,12 @@ contract KlerosCore is IArbitrator {
return disputeKit.currentRuling(_disputeID);
}
+ function getDispute(uint256 _disputeID) external view returns (Round[] memory) {
+ return disputes[_disputeID].rounds;
+ }
+
// ************************************* //
- // * Getters for dispute kits * //
+ // * Public Views for Dispute Kits * //
// ************************************* //
function getSortitionSumTree(bytes32 _key)
@@ -690,7 +699,9 @@ contract KlerosCore is IArbitrator {
return disputes[_disputeID].ruled;
}
- /* Internal */
+ // ************************************* //
+ // * Internal * //
+ // ************************************* //
/** @dev Sets the specified juror's stake in a subcourt.
* `O(n + p * log_k(j))` where
@@ -702,7 +713,7 @@ contract KlerosCore is IArbitrator {
* @param _subcourtID The ID of the subcourt.
* @param _stake The new stake.
*/
- function _setStake(
+ function setStakeForAccount(
address _account,
uint96 _subcourtID,
uint256 _stake
@@ -820,21 +831,4 @@ contract KlerosCore is IArbitrator {
stakePathID := mload(ptr)
}
}
-
- function getDispute(uint256 _disputeID)
- external
- view
- returns (
- uint256[] memory tokensAtStakePerJuror,
- uint256[] memory totalFeesForJurors,
- uint256[] memory repartitionsInEachRound,
- uint256[] memory penaltiesInEachRound
- )
- {
- Dispute storage dispute = disputes[_disputeID];
- tokensAtStakePerJuror = dispute.tokensAtStakePerJuror;
- totalFeesForJurors = dispute.totalFeesForJurors;
- repartitionsInEachRound = dispute.repartitionsInEachRound;
- penaltiesInEachRound = dispute.penaltiesInEachRound;
- }
}
diff --git a/contracts/src/arbitration/dispute-kits/DisputeKitClassic.sol b/contracts/src/arbitration/dispute-kits/DisputeKitClassic.sol
index 051e9f3bb..99b43fb57 100644
--- a/contracts/src/arbitration/dispute-kits/DisputeKitClassic.sol
+++ b/contracts/src/arbitration/dispute-kits/DisputeKitClassic.sol
@@ -17,10 +17,10 @@ import "../../rng/RNG.sol";
/**
* @title DisputeKitClassic
* Dispute kit implementation of the Kleros v1 features including:
- * - Drawing system: proportional to staked PNK,
- * - Vote aggreation system: plurality,
- * - Incentive system: equal split between coherent votes,
- * - Appeal system: fund 2 choices only, vote on any choice.
+ * - a drawing system: proportional to staked PNK,
+ * - a vote aggreation system: plurality,
+ * - an incentive system: equal split between coherent votes,
+ * - an appeal system: fund 2 choices only, vote on any choice.
*/
contract DisputeKitClassic is DisputeKit {
// ************************************* //
@@ -361,7 +361,7 @@ contract DisputeKitClassic is DisputeKit {
if (msg.value > contribution) payable(msg.sender).send(msg.value - contribution);
}
- /** @dev Allows to withdraw any reimbursable fees or rewards after the dispute gets resolved.
+ /** @dev Allows those contributors who attempted to fund an appeal round to withdraw any reimbursable fees or rewards after the dispute gets resolved.
* @param _disputeID Index of the dispute in Kleros Core contract.
* @param _beneficiary The address whose rewards to withdraw.
* @param _round The round the caller wants to withdraw from.
@@ -528,13 +528,13 @@ contract DisputeKitClassic is DisputeKit {
}
// ************************************* //
- // * Internal/Private * //
+ // * Internal * //
// ************************************* //
/** @dev RNG function
* @return rn A random number.
*/
- function getRandomNumber() private returns (uint256) {
+ function getRandomNumber() internal returns (uint256) {
return rng.getUncorrelatedRN(block.number);
}
@@ -542,7 +542,7 @@ contract DisputeKitClassic is DisputeKit {
* @param _stakePathID The stake path ID to unpack.
* @return account The account.
*/
- function stakePathIDToAccount(bytes32 _stakePathID) private pure returns (address account) {
+ function stakePathIDToAccount(bytes32 _stakePathID) internal pure returns (address account) {
assembly {
// solium-disable-line security/no-inline-assembly
let ptr := mload(0x40)
From 62e093d6517b6e571b3985c6e72aaee03835396f Mon Sep 17 00:00:00 2001
From: jaybuidl
Date: Thu, 30 Dec 2021 03:26:36 +0000
Subject: [PATCH 11/14] feat(DisputeKitSybilResistant): naive sybil-resistant
juror drawing
work in progress
---
.../dispute-kits/DisputeKitClassic.sol | 14 +-
.../dispute-kits/DisputeKitSybilResistant.sol | 590 ++++++++++++++++++
2 files changed, 598 insertions(+), 6 deletions(-)
create mode 100644 contracts/src/arbitration/dispute-kits/DisputeKitSybilResistant.sol
diff --git a/contracts/src/arbitration/dispute-kits/DisputeKitClassic.sol b/contracts/src/arbitration/dispute-kits/DisputeKitClassic.sol
index 99b43fb57..d8b6f837b 100644
--- a/contracts/src/arbitration/dispute-kits/DisputeKitClassic.sol
+++ b/contracts/src/arbitration/dispute-kits/DisputeKitClassic.sol
@@ -193,22 +193,24 @@ contract DisputeKitClassic is DisputeKit {
Round storage round = dispute.rounds[dispute.rounds.length - 1];
// TODO: Handle the situation when no one has staked yet.
- while (
- (K * treeIndex) + 1 < nodes.length // While it still has children.
- )
+
+ // While it still has children
+ while ((K * treeIndex) + 1 < nodes.length) {
for (uint256 i = 1; i <= K; i++) {
// Loop over children.
uint256 nodeIndex = (K * treeIndex) + i;
uint256 nodeValue = nodes[nodeIndex];
- if (currentDrawnNumber >= nodeValue)
- currentDrawnNumber -= nodeValue; // Go to the next child.
- else {
+ if (currentDrawnNumber >= nodeValue) {
+ // Go to the next child.
+ currentDrawnNumber -= nodeValue;
+ } else {
// Pick this child.
treeIndex = nodeIndex;
break;
}
}
+ }
bytes32 ID = core.getSortitionSumTreeID(key, treeIndex);
drawnAddress = stakePathIDToAccount(ID);
diff --git a/contracts/src/arbitration/dispute-kits/DisputeKitSybilResistant.sol b/contracts/src/arbitration/dispute-kits/DisputeKitSybilResistant.sol
new file mode 100644
index 000000000..7ce63280a
--- /dev/null
+++ b/contracts/src/arbitration/dispute-kits/DisputeKitSybilResistant.sol
@@ -0,0 +1,590 @@
+// SPDX-License-Identifier: MIT
+
+/**
+ * @authors: [@unknownunknown1, @jaybuidl]
+ * @reviewers: []
+ * @auditors: []
+ * @bounties: []
+ * @deployments: []
+ */
+
+pragma solidity ^0.8;
+
+import "./DisputeKit.sol";
+import "../KlerosCore.sol";
+import "../../rng/RNG.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 aggreation system: plurality,
+ * - an incentive system: equal split between coherent votes,
+ * - an appeal system: fund 2 choices only, vote on any choice.
+ */
+contract DisputeKitSybilResistant is DisputeKit {
+ // ************************************* //
+ // * 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".
+ }
+
+ 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 => uint256) paidFees; // Tracks the fees paid for each choice in this round.
+ mapping(uint256 => bool) hasPaid; // True if this choice was fully funded, false otherwise.
+ mapping(address => mapping(uint256 => 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.
+ }
+
+ 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
+ RNG public rng; // The random number generator
+ IProofOfHumanity public poh; // The Proof of Humanity registry
+ 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.
+
+ // ************************************* //
+ // * Events * //
+ // ************************************* //
+
+ event Contribution(
+ uint256 indexed _disputeID,
+ uint256 indexed _round,
+ uint256 _choice,
+ address indexed _contributor,
+ uint256 _amount
+ );
+
+ event Withdrawal(
+ uint256 indexed _disputeID,
+ uint256 indexed _round,
+ uint256 _choice,
+ address indexed _contributor,
+ uint256 _amount
+ );
+
+ event ChoiceFunded(uint256 indexed _disputeID, uint256 indexed _round, uint256 indexed _choice);
+
+ // ************************************* //
+ // * Function Modifiers * //
+ // ************************************* //
+
+ modifier onlyByCore() {
+ require(address(core) == msg.sender, "Access not allowed: KlerosCore only.");
+ _;
+ }
+
+ modifier onlyByGovernor() {
+ require(governor == msg.sender, "Access not allowed: Governor only.");
+ _;
+ }
+
+ /** @dev Constructor.
+ * @param _governor The governor's address.
+ * @param _core The KlerosCore arbitrator.
+ * @param _rng The random number generator.
+ */
+ constructor(
+ address _governor,
+ KlerosCore _core,
+ RNG _rng,
+ IProofOfHumanity _poh
+ ) {
+ governor = _governor;
+ core = _core;
+ rng = _rng;
+ poh = _poh;
+ }
+
+ // ************************ //
+ // * Governance * //
+ // ************************ //
+
+ /** @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 _disputeID 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.
+ */
+ function createDispute(
+ uint256 _disputeID,
+ uint256 _numberOfChoices,
+ bytes calldata _extraData
+ ) external override onlyByCore {
+ uint256 localDisputeID = disputes.length;
+ Dispute storage dispute = disputes.push();
+ dispute.numberOfChoices = _numberOfChoices;
+
+ Round storage round = dispute.rounds.push();
+ round.tied = true;
+
+ coreDisputeIDToLocal[_disputeID] = localDisputeID;
+ }
+
+ /** @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 _disputeID The ID of the dispute in Kleros Core.
+ * @return drawnAddress The drawn address.
+ */
+ function draw(uint256 _disputeID) external override onlyByCore returns (address drawnAddress) {
+ bytes32 key = bytes32(core.getSubcourtID(_disputeID)); // Get the ID of the tree.
+ uint256 drawnNumber = getRandomNumber();
+
+ (uint256 K, , uint256[] memory nodes) = core.getSortitionSumTree(key);
+ uint256 treeIndex = 0;
+ uint256 currentDrawnNumber = drawnNumber % nodes[0];
+
+ Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]];
+ Round storage round = dispute.rounds[dispute.rounds.length - 1];
+
+ // TODO: Handle the situation when no one has staked yet.
+
+ // While it still has children
+ while ((K * treeIndex) + 1 < nodes.length) {
+ for (uint256 i = 1; i <= K; i++) {
+ // Loop over children.
+ uint256 nodeIndex = (K * treeIndex) + i;
+ uint256 nodeValue = nodes[nodeIndex];
+
+ if (currentDrawnNumber >= nodeValue) {
+ // Go to the next child.
+ currentDrawnNumber -= nodeValue;
+ } else {
+ // Pick this child.
+ treeIndex = nodeIndex;
+ break;
+ }
+ }
+ }
+
+ bytes32 ID = core.getSortitionSumTreeID(key, treeIndex);
+ drawnAddress = stakePathIDToAccount(ID);
+
+ if (!proofOfHumanity(drawnAddress)) drawnAddress = address(0);
+ // TODO: deduplicate the list of all the drawn humans before moving to the next period !!
+
+ round.votes.push(Vote({account: drawnAddress, commit: bytes32(0), choice: 0, voted: false}));
+ }
+
+ /** @dev Sets the caller's commit for the specified votes.
+ * `O(n)` where
+ * `n` is the number of votes.
+ * @param _disputeID The ID of the dispute.
+ * @param _voteIDs The IDs of the votes.
+ * @param _commit The commit.
+ */
+ function castCommit(
+ uint256 _disputeID,
+ uint256[] calldata _voteIDs,
+ bytes32 _commit
+ ) external override {
+ require(
+ core.getCurrentPeriod(_disputeID) == KlerosCore.Period.commit,
+ "The dispute should be in Commit period."
+ );
+ require(_commit != bytes32(0), "Empty commit.");
+
+ Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]];
+ 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.");
+ require(round.votes[_voteIDs[i]].commit == bytes32(0), "Already committed this vote.");
+ round.votes[_voteIDs[i]].commit = _commit;
+ }
+ round.totalCommitted += _voteIDs.length;
+
+ if (round.totalCommitted == round.votes.length) core.passPeriod(_disputeID);
+ }
+
+ /** @dev Sets the caller's choices for the specified votes.
+ * `O(n)` where
+ * `n` is the number of votes.
+ * @param _disputeID The ID of the dispute.
+ * @param _voteIDs The IDs of the votes.
+ * @param _choice The choice.
+ * @param _salt The salt for the commit if the votes were hidden.
+ */
+ function castVote(
+ uint256 _disputeID,
+ uint256[] calldata _voteIDs,
+ uint256 _choice,
+ uint256 _salt
+ ) external override {
+ require(core.getCurrentPeriod(_disputeID) == KlerosCore.Period.vote, "The dispute should be in Vote period.");
+ require(_voteIDs.length > 0, "No voteID provided");
+
+ Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]];
+ require(_choice <= dispute.numberOfChoices, "Choice out of bounds");
+
+ Round storage round = dispute.rounds[dispute.rounds.length - 1];
+ bool hiddenVotes = core.areVotesHidden(core.getSubcourtID(_disputeID));
+
+ // 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 subcourts 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;
+ }
+ }
+
+ // Automatically switch period when voting is finished.
+ if (round.totalVoted == round.votes.length) core.passPeriod(_disputeID);
+ }
+
+ /** @dev Manages contributions, and appeals a dispute if at least two choices are fully funded.
+ * Note that the surplus deposit will be reimbursed.
+ * @param _disputeID Index of the dispute in Kleros Core contract.
+ * @param _choice A choice that receives funding.
+ */
+ function fundAppeal(uint256 _disputeID, uint256 _choice) external payable override {
+ Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]];
+ require(_choice <= dispute.numberOfChoices, "There is no such ruling to fund.");
+
+ (uint256 appealPeriodStart, uint256 appealPeriodEnd) = core.appealPeriod(_disputeID);
+ require(block.timestamp >= appealPeriodStart && block.timestamp < appealPeriodEnd, "Appeal period is over.");
+
+ uint256 multiplier;
+ if (currentRuling(_disputeID) == _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];
+ require(!round.hasPaid[_choice], "Appeal fee is already paid.");
+ uint256 appealCost = core.appealCost(_disputeID);
+ 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(_disputeID, dispute.rounds.length - 1, _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(_disputeID, dispute.rounds.length - 1, _choice);
+ }
+
+ if (round.fundedChoices.length > 1) {
+ // At least two sides are fully funded.
+ round.feeRewards = round.feeRewards - appealCost;
+
+ Round storage newRound = dispute.rounds.push();
+ newRound.tied = true;
+ core.appeal{value: appealCost}(_disputeID);
+ }
+
+ 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.
+ * @param _disputeID Index of the dispute in Kleros Core contract.
+ * @param _beneficiary The address whose rewards to withdraw.
+ * @param _round The round 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 _disputeID,
+ address payable _beneficiary,
+ uint256 _round,
+ uint256 _choice
+ ) external override returns (uint256 amount) {
+ require(core.isRuled(_disputeID), "Dispute should be resolved.");
+
+ Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]];
+ Round storage round = dispute.rounds[_round];
+ uint256 finalRuling = currentRuling(_disputeID);
+
+ 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(_disputeID, _round, _choice, _beneficiary, amount);
+ }
+ }
+
+ // ************************************* //
+ // * Public Views * //
+ // ************************************* //
+
+ /** @dev Gets the current ruling of a specified dispute.
+ * @param _disputeID The ID of the dispute in Kleros Core.
+ * @return ruling The current ruling.
+ */
+ function currentRuling(uint256 _disputeID) public view override returns (uint256 ruling) {
+ Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]];
+ Round storage round = dispute.rounds[dispute.rounds.length - 1];
+ ruling = round.tied ? 0 : round.winningChoice;
+ }
+
+ /** @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 _disputeID The ID of the dispute in Kleros Core.
+ * @param _round The ID of the round.
+ * @param _voteID The ID of the vote.
+ * @return The degree of coherence in basis points.
+ */
+ function getDegreeOfCoherence(
+ uint256 _disputeID,
+ uint256 _round,
+ uint256 _voteID
+ ) 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[_disputeID]];
+ Round storage lastRound = dispute.rounds[dispute.rounds.length - 1];
+ Vote storage vote = dispute.rounds[_round].votes[_voteID];
+
+ if (vote.voted && (vote.choice == lastRound.winningChoice || lastRound.tied)) {
+ return ONE_BASIS_POINT;
+ } else {
+ return 0;
+ }
+ }
+
+ /** @dev Gets the number of jurors who are eligible to a reward in this round.
+ * @param _disputeID The ID of the dispute in Kleros Core.
+ * @param _round The ID of the round.
+ * @return The number of coherent jurors.
+ */
+ function getCoherentCount(uint256 _disputeID, uint256 _round) external view override returns (uint256) {
+ Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]];
+ Round storage lastRound = dispute.rounds[dispute.rounds.length - 1];
+ Round storage currentRound = dispute.rounds[_round];
+ uint256 winningChoice = lastRound.winningChoice;
+
+ if (currentRound.totalVoted == 0 || (!lastRound.tied && currentRound.counts[winningChoice] == 0)) {
+ return 0;
+ } else if (lastRound.tied) {
+ return currentRound.totalVoted;
+ } else {
+ return currentRound.counts[winningChoice];
+ }
+ }
+
+ /** @dev Returns true if the specified voter was active in this round.
+ * @param _disputeID The ID of the dispute in Kleros Core.
+ * @param _round The ID of the round.
+ * @param _voteID The ID of the voter.
+ * @return Whether the voter was active or not.
+ */
+ function isVoteActive(
+ uint256 _disputeID,
+ uint256 _round,
+ uint256 _voteID
+ ) external view override returns (bool) {
+ Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]];
+ Vote storage vote = dispute.rounds[_round].votes[_voteID];
+ return vote.voted;
+ }
+
+ function getRoundInfo(
+ uint256 _disputeID,
+ uint256 _round,
+ uint256 _choice
+ )
+ external
+ view
+ override
+ returns (
+ uint256 winningChoice,
+ bool tied,
+ uint256 totalVoted,
+ uint256 totalCommited,
+ uint256 nbVoters,
+ uint256 choiceCount
+ )
+ {
+ Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]];
+ Round storage round = dispute.rounds[_round];
+ return (
+ round.winningChoice,
+ round.tied,
+ round.totalVoted,
+ round.totalCommitted,
+ round.votes.length,
+ round.counts[_choice]
+ );
+ }
+
+ function getVoteInfo(
+ uint256 _disputeID,
+ uint256 _round,
+ uint256 _voteID
+ )
+ external
+ view
+ override
+ returns (
+ address account,
+ bytes32 commit,
+ uint256 choice,
+ bool voted
+ )
+ {
+ Dispute storage dispute = disputes[coreDisputeIDToLocal[_disputeID]];
+ Vote storage vote = dispute.rounds[_round].votes[_voteID];
+ return (vote.account, vote.commit, vote.choice, vote.voted);
+ }
+
+ // ************************************* //
+ // * Internal * //
+ // ************************************* //
+
+ /** @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);
+ }
+
+ /** @dev RNG function
+ * @return rn A random number.
+ */
+ function getRandomNumber() internal returns (uint256) {
+ return rng.getUncorrelatedRN(block.number);
+ }
+
+ /** @dev Retrieves a juror's address from the stake path ID.
+ * @param _stakePathID The stake path ID to unpack.
+ * @return account The account.
+ */
+ function stakePathIDToAccount(bytes32 _stakePathID) internal pure returns (address account) {
+ assembly {
+ // solium-disable-line security/no-inline-assembly
+ let ptr := mload(0x40)
+ for {
+ let i := 0x00
+ } lt(i, 0x14) {
+ i := add(i, 0x01)
+ } {
+ mstore8(add(add(ptr, 0x0c), i), byte(i, _stakePathID))
+ }
+ account := mload(ptr)
+ }
+ }
+}
From 71bf37121a3a76c6063422946044de9f4e6b0059 Mon Sep 17 00:00:00 2001
From: jaybuidl
Date: Mon, 17 Jan 2022 14:37:23 +0000
Subject: [PATCH 12/14] chore(Code Climate): disable TS quote check
No guideline on single vs. double quotes for now.
---
.codeclimate.yml | 6 ++++++
1 file changed, 6 insertions(+)
create mode 100644 .codeclimate.yml
diff --git a/.codeclimate.yml b/.codeclimate.yml
new file mode 100644
index 000000000..a9bacd6ee
--- /dev/null
+++ b/.codeclimate.yml
@@ -0,0 +1,6 @@
+engines:
+ tslint:
+ enabled: true
+ checks:
+ quotemark:
+ enabled: false
From 711b52699e30bc6adc3b827819741de7f1e2b3b1 Mon Sep 17 00:00:00 2001
From: jaybuidl
Date: Thu, 6 Jan 2022 01:07:59 +0000
Subject: [PATCH 13/14] chore(contracts): github workflow for contracts testing
(#10)
---
.github/workflows/contracts-testing.yml | 60 ++++++++++++
README.md | 1 +
contracts/.gitignore | 1 +
contracts/.solcover.js | 19 ++++
contracts/coverage.json | 122 ------------------------
5 files changed, 81 insertions(+), 122 deletions(-)
create mode 100644 .github/workflows/contracts-testing.yml
create mode 100644 contracts/.solcover.js
delete mode 100644 contracts/coverage.json
diff --git a/.github/workflows/contracts-testing.yml b/.github/workflows/contracts-testing.yml
new file mode 100644
index 000000000..3736f753d
--- /dev/null
+++ b/.github/workflows/contracts-testing.yml
@@ -0,0 +1,60 @@
+name: Contracts Testing
+
+on:
+ workflow_dispatch:
+ push:
+ branches:
+ - master
+ pull_request:
+ branches:
+ - "*"
+
+jobs:
+ test:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Setup Node.js environment
+ uses: actions/setup-node@v2.5.1
+ with:
+ node-version: 16.x
+
+ - uses: actions/checkout@v2.4.0
+
+ - name: Cache node modules
+ uses: actions/cache@v2.1.7
+ env:
+ cache-name: cache-node-modules
+ with:
+ path: |
+ ~/.npm
+ **/node_modules
+ key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json', '**/yarn.lock') }}
+ restore-keys: |
+ ${{ runner.os }}-build-${{ env.cache-name }}-
+ ${{ runner.os }}-build-
+ ${{ runner.os }}-
+
+ #- name: Install parent dependencies
+ # run: |
+ # echo "current dir: $PWD"
+ # yarn install
+
+ - name: Install contracts dependencies
+ run: |
+ yarn workspace @kleros/kleros-v2-contracts install
+
+ - name: Compile
+ run: |
+ npx hardhat compile
+ working-directory: contracts
+
+ - name: Test with coverage
+ run: |
+ npx hardhat coverage --solcoverjs ./.solcover.js --temp artifacts --testfiles \"./test/**/*.ts\"
+ working-directory: contracts
+
+ - name: Upload a build artifact
+ uses: actions/upload-artifact@v2.3.1
+ with:
+ name: code-coverage-report
+ path: contracts/coverage
diff --git a/README.md b/README.md
index dd8d06cfa..49090207d 100644
--- a/README.md
+++ b/README.md
@@ -9,6 +9,7 @@
+
diff --git a/contracts/.gitignore b/contracts/.gitignore
index 1d29ae4ea..18aa4922d 100644
--- a/contracts/.gitignore
+++ b/contracts/.gitignore
@@ -1,6 +1,7 @@
# Hardhat files
cache
artifacts
+coverage.json
## Hardhat typechain bindings/types
typechain
diff --git a/contracts/.solcover.js b/contracts/.solcover.js
new file mode 100644
index 000000000..43bd929a3
--- /dev/null
+++ b/contracts/.solcover.js
@@ -0,0 +1,19 @@
+/* eslint-disable node/no-extraneous-require */
+
+const shell = require("shelljs");
+
+// The environment variables are loaded in hardhat.config.ts
+
+module.exports = {
+ istanbulReporter: ["html"],
+ onCompileComplete: async function (_config) {
+ await run("typechain");
+ },
+ onIstanbulComplete: async function (_config) {
+ // We need to do this because solcover generates bespoke artifacts.
+ shell.rm("-rf", "./artifacts");
+ shell.rm("-rf", "./typechain");
+ },
+ providerOptions: {},
+ skipFiles: ["mocks", "test"],
+};
diff --git a/contracts/coverage.json b/contracts/coverage.json
deleted file mode 100644
index 75bd461c7..000000000
--- a/contracts/coverage.json
+++ /dev/null
@@ -1,122 +0,0 @@
-{
- "src/Greeter.sol": {
- "l": {
- "10": 3,
- "11": 3,
- "15": 2,
- "19": 1,
- "20": 1
- },
- "path": "/Users/jaybee/project/kleros/kleros-v2/contracts/src/Greeter.sol",
- "s": {
- "1": 3,
- "2": 3,
- "3": 2,
- "4": 1,
- "5": 1
- },
- "b": {},
- "f": {
- "1": 3,
- "2": 2,
- "3": 1
- },
- "fnMap": {
- "1": {
- "name": "constructor",
- "line": 9,
- "loc": {
- "start": {
- "line": 9,
- "column": 4
- },
- "end": {
- "line": 12,
- "column": 4
- }
- }
- },
- "2": {
- "name": "greet",
- "line": 14,
- "loc": {
- "start": {
- "line": 14,
- "column": 4
- },
- "end": {
- "line": 16,
- "column": 4
- }
- }
- },
- "3": {
- "name": "setGreeting",
- "line": 18,
- "loc": {
- "start": {
- "line": 18,
- "column": 4
- },
- "end": {
- "line": 21,
- "column": 4
- }
- }
- }
- },
- "statementMap": {
- "1": {
- "start": {
- "line": 10,
- "column": 8
- },
- "end": {
- "line": 10,
- "column": 67
- }
- },
- "2": {
- "start": {
- "line": 11,
- "column": 8
- },
- "end": {
- "line": 11,
- "column": 27
- }
- },
- "3": {
- "start": {
- "line": 15,
- "column": 8
- },
- "end": {
- "line": 15,
- "column": 23
- }
- },
- "4": {
- "start": {
- "line": 19,
- "column": 8
- },
- "end": {
- "line": 19,
- "column": 78
- }
- },
- "5": {
- "start": {
- "line": 20,
- "column": 8
- },
- "end": {
- "line": 20,
- "column": 27
- }
- }
- },
- "branchMap": {}
- }
-}
From 1a5d4fd35a8b1befb6915767a4fdbb30dbe6aaa1 Mon Sep 17 00:00:00 2001
From: jaybuidl
Date: Thu, 6 Jan 2022 01:18:34 +0000
Subject: [PATCH 14/14] docs: pinned the Contracts Testing badge to master
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 49090207d..33c295928 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,7 @@
-
+