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 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/contracts/hardhat.config.ts b/contracts/hardhat.config.ts index fbd66b057..14cf7cbf6 100644 --- a/contracts/hardhat.config.ts +++ b/contracts/hardhat.config.ts @@ -6,8 +6,10 @@ 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"; +import "hardhat-docgen"; dotenv.config(); @@ -43,6 +45,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 +115,25 @@ 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/**/*"], + }, + }, + docgen: { + path: './docs', + clear: true, + runOnCompile: false, + }, }; export default config; diff --git a/contracts/package.json b/contracts/package.json index db4f4108a..cd48a9cb8 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -14,12 +14,15 @@ "build": "hardhat compile", "clean": "hardhat clean", "deploy": "hardhat deploy", - "test": "hardhat test" + "test": "hardhat test", + "watch": "hardhat watch", + "docgen": "hardhat docgen" }, "devDependencies": { "@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", @@ -35,7 +38,9 @@ "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", "mocha": "^9.1.3", "solhint": "^3.3.6", 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/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/KlerosCore.sol b/contracts/src/arbitration/KlerosCore.sol new file mode 100644 index 000000000..cad626e1c --- /dev/null +++ b/contracts/src/arbitration/KlerosCore.sol @@ -0,0 +1,834 @@ +// SPDX-License-Identifier: MIT + +/** + * @authors: [@unknownunknown1] + * @reviewers: [] + * @auditors: [] + * @bounties: [] + * @deployments: [] + */ + +pragma solidity ^0.8; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "./IArbitrator.sol"; +import "./dispute-kits/DisputeKit.sol"; +import {SortitionSumTreeFactory} from "../data-structures/SortitionSumTreeFactory.sol"; + +/** + * @title KlerosCore + * Core arbitrator contract for Kleros v2. + */ +contract KlerosCore is IArbitrator { + using SortitionSumTreeFactory for SortitionSumTreeFactory.SortitionSumTrees; // Use library functions for sortition sum trees. + + // ************************************* // + // * Enums / Structs * // + // ************************************* // + + 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. + } + + 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]`. + 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 { + 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]`. + } + + // ************************************* // + // * 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. + + 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 + ); + + // ************************************* // + // * Function Modifiers * // + // ************************************* // + + modifier onlyByGovernor() { + require(governor == msg.sender, "Access not allowed: Governor only."); + _; + } + + /** @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 + ) { + 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); + } + + // ************************ // + // * 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; + } + } + + // ************************************* // + // * 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 { + setStakeForAccount(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; + + 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)); + } + + /** @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 + .rounds[dispute.currentRound] + .tokensAtStakePerJuror; + dispute.drawnJurors[dispute.currentRound].push(drawnAddress); + emit Draw(drawnAddress, _disputeID, dispute.currentRound, i); + } + } + } + + /** @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), "Access not allowed: Dispute Kit only."); + + 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; + + Round storage extraRound = dispute.rounds.push(); + extraRound.tokensAtStakePerJuror = + (courts[dispute.subcourtID].minStake * courts[dispute.subcourtID].alpha) / + ALPHA_DIVISOR; + extraRound.totalFeesForJurors = msg.value; + + 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.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. + + 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.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.rounds[_appeal].tokensAtStakePerJuror * + (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++) + 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.rounds[_appeal].totalFeesForJurors); + 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.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.rounds[_appeal].totalFeesForJurors / coherentCount) * degreeOfCoherence) / + ALPHA_DIVISOR; + + pinakion.transfer(account, tokenReward); + payable(account).send(ETHReward); + emit TokenAndETHShift(account, _disputeID, int256(tokenReward), int256(ETHReward)); + } + } + + if (dispute.rounds[_appeal].penalties != penaltiesInRoundCache) + dispute.rounds[_appeal].penalties = penaltiesInRoundCache; + dispute.rounds[_appeal].repartitions = 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); + } + + // ************************************* // + // * 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) + * 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); + } + + function getDispute(uint256 _disputeID) external view returns (Round[] memory) { + return disputes[_disputeID].rounds; + } + + // ************************************* // + // * Public Views 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 setStakeForAccount( + 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) + } + } +} diff --git a/contracts/src/arbitration/dispute-kits/DisputeKit.sol b/contracts/src/arbitration/dispute-kits/DisputeKit.sol new file mode 100644 index 000000000..1b5d8e736 --- /dev/null +++ b/contracts/src/arbitration/dispute-kits/DisputeKit.sol @@ -0,0 +1,164 @@ +// SPDX-License-Identifier: MIT + +/** + * @authors: [@unknownunknown1, @jaybuidl] + * @reviewers: [] + * @auditors: [] + * @bounties: [] + * @deployments: [] + */ + +pragma solidity ^0.8; + +import "../IArbitrator.sol"; + +/** + * @title DisputeKit + * Dispute kit abstraction for Kleros v2. + */ +abstract contract DisputeKit { + // ************************************* // + // * 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 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 virtual returns (address drawnAddress); + + /** @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 virtual; + + /** @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 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 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. + * @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 virtual returns (uint256 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 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. + * @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 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 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. + * @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 virtual returns (bool); + + function getRoundInfo( + uint256 _disputeID, + uint256 _round, + uint256 _choice + ) + external + view + virtual + returns ( + uint256 winningChoice, + bool tied, + uint256 totalVoted, + uint256 totalCommited, + uint256 nbVoters, + uint256 choiceCount + ); + + function getVoteInfo( + uint256 _disputeID, + uint256 _round, + uint256 _voteID + ) + external + view + virtual + returns ( + address account, + bytes32 commit, + uint256 choice, + bool voted + ); +} diff --git a/contracts/src/arbitration/dispute-kits/DisputeKitClassic.sol b/contracts/src/arbitration/dispute-kits/DisputeKitClassic.sol new file mode 100644 index 000000000..d8b6f837b --- /dev/null +++ b/contracts/src/arbitration/dispute-kits/DisputeKitClassic.sol @@ -0,0 +1,561 @@ +// 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: + * - 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 { + // ************************************* // + // * 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 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); + + 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 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) + } + } +} 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) + } + } +} diff --git a/contracts/src/data-structures/SortitionSumTreeFactory.sol b/contracts/src/data-structures/SortitionSumTreeFactory.sol new file mode 100644 index 000000000..4c58af45b --- /dev/null +++ b/contracts/src/data-structures/SortitionSumTreeFactory.sol @@ -0,0 +1,241 @@ +// 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 */ + + 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; + } + + /* 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 + ) 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.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/contracts/test/arbitration/index.ts b/contracts/test/arbitration/index.ts new file mode 100644 index 000000000..f00e76008 --- /dev/null +++ b/contracts/test/arbitration/index.ts @@ -0,0 +1,80 @@ +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; + +describe("DisputeKitClassic", 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); + }); + + it("Should create a dispute", async function () { + 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); + + 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 constantNGFactory = await ethers.getContractFactory("ConstantNG", deployer); + const rng = await constantNGFactory.deploy(42); + await rng.deployed(); + + const disputeKitFactory = await ethers.getContractFactory("DisputeKitClassic", deployer); + const disputeKit = await disputeKitFactory.deploy( + deployer.address, + 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 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); + await arbitrable.deployed(); + + return [core, disputeKit, arbitrable]; +} 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 0ee5ec37e..8b4a789d0 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 @@ -1010,7 +1011,9 @@ __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 mocha: ^9.1.3 solhint: ^3.3.6 @@ -1110,6 +1113,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" @@ -1443,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" @@ -1462,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 @@ -1835,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" @@ -1855,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" @@ -1969,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" @@ -2077,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: @@ -3223,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" @@ -3275,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 @@ -3321,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" @@ -3448,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" @@ -3654,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" @@ -3693,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" @@ -3807,7 +4100,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: @@ -3840,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" @@ -3889,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" @@ -4107,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 @@ -4157,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" @@ -4600,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" @@ -4633,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" @@ -4988,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" @@ -4995,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" @@ -5064,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" @@ -5100,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" @@ -5157,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" @@ -5166,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" @@ -5228,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" @@ -5446,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 @@ -6301,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 @@ -7304,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" @@ -7492,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 @@ -7591,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" @@ -7603,6 +8101,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" @@ -7795,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" @@ -7815,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: @@ -7868,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" @@ -8004,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" @@ -8755,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" @@ -8859,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 @@ -9534,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" @@ -9677,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" @@ -9709,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" @@ -9943,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" @@ -10060,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: @@ -10553,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 @@ -10574,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" @@ -10659,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" @@ -10777,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" @@ -11177,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" @@ -11251,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" @@ -11429,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" @@ -11514,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" @@ -11525,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" @@ -11601,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" @@ -11610,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" @@ -11700,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 @@ -12191,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" @@ -12643,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" @@ -12791,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: @@ -13235,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" @@ -13277,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" @@ -13291,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 @@ -13307,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" @@ -13697,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: @@ -13804,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" @@ -13875,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" @@ -14202,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 @@ -14645,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 @@ -14679,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" @@ -14753,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" @@ -15355,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" @@ -15716,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"