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 @@

+ Unit testing Conventional Commits Commitizen Friendly Styled with Prettier 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 @@

- Unit testing + Unit testing Conventional Commits Commitizen Friendly Styled with Prettier