From ed930de0b146b24241742076a8df2cd12263126e Mon Sep 17 00:00:00 2001 From: unknownunknown1 Date: Thu, 18 Nov 2021 03:07:05 +1000 Subject: [PATCH 1/6] feat(Arbitration): standard update --- CHANGELOG.md | 4 +- .../src/arbitration/ArbitrableExample.sol | 68 ++++ .../src/arbitration/CentralizedArbitrator.sol | 373 ++++++++++++++++++ contracts/src/arbitration/IArbitrable.sol | 30 ++ contracts/src/arbitration/IArbitrator.sol | 98 +++++ 5 files changed, 572 insertions(+), 1 deletion(-) create mode 100644 contracts/src/arbitration/ArbitrableExample.sol create mode 100644 contracts/src/arbitration/CentralizedArbitrator.sol create mode 100644 contracts/src/arbitration/IArbitrable.sol create mode 100644 contracts/src/arbitration/IArbitrator.sol diff --git a/CHANGELOG.md b/CHANGELOG.md index 324ce729a..58a664272 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ -## 0.1.0 (2021-11-11) +## 0.1.0 (2021-11-17) +- chore: added GitHub code scanning ([4a70475](https://github.com/kleros/kleros-v2/commit/4a70475)) +- chore: added the hardhat config for layer 2 networks, added hardhat-deploy and mocha ([a12ea0e](https://github.com/kleros/kleros-v2/commit/a12ea0e)) - test: added a test for IncrementalNG ([65a996b](https://github.com/kleros/kleros-v2/commit/65a996b)) - docs: initial commit ([23356e7](https://github.com/kleros/kleros-v2/commit/23356e7)) - docs: license file added ([cb62d2c](https://github.com/kleros/kleros-v2/commit/cb62d2c)) diff --git a/contracts/src/arbitration/ArbitrableExample.sol b/contracts/src/arbitration/ArbitrableExample.sol new file mode 100644 index 000000000..328ea3b13 --- /dev/null +++ b/contracts/src/arbitration/ArbitrableExample.sol @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8; + +import "./IArbitrable.sol"; + +/** + * @title ArbitrableExample + * An example of an arbitrable contract which connects to the arbitator that implements the updated interface. + */ +contract ArbitrableExample is IArbitrable { + struct DisputeStruct { + bool isRuled; // Whether the dispute has been ruled or not. + uint256 ruling; // Ruling given by the arbitrator. + uint256 numberOfRulingOptions; // The number of choices the arbitrator can give. + } + + IArbitrator public immutable arbitrator; // Arbitrator is set in constructor and never changed. + + mapping(uint256 => uint256) public externalIDtoLocalID; // Maps external (arbitrator side) dispute IDs to local dispute IDs. + + DisputeStruct[] public disputes; // Stores the disputes' info. disputes[disputeID]. + + /** @dev Constructor + * @param _arbitrator The arbitrator to rule on created disputes. + */ + constructor(IArbitrator _arbitrator) { + arbitrator = _arbitrator; + } + + /** @dev TRUSTED. Calls createDispute function of the specified arbitrator to create a dispute. + Note that we don’t need to check that msg.value is enough to pay arbitration fees as it’s the responsibility of the arbitrator contract. + * @param _numberOfRulingOptions Number of ruling options. Must be greater than 1, otherwise there is nothing to choose from. + * @param _arbitratorExtraData Extra data for the arbitrator. + * @return disputeID Dispute id (on arbitrator side) of the dispute created. + */ + function createDispute(uint256 _numberOfRulingOptions, bytes calldata _arbitratorExtraData) + external + payable + returns (uint256 disputeID) + { + require(_numberOfRulingOptions > 1, "Incorrect number of choices"); + + uint256 localDisputeID = disputes.length; + disputes.push(DisputeStruct({isRuled: false, ruling: 0, numberOfRulingOptions: _numberOfRulingOptions})); + + disputeID = arbitrator.createDispute{value: msg.value}(_numberOfRulingOptions, _arbitratorExtraData); + + externalIDtoLocalID[disputeID] = localDisputeID; + } + + /** @dev To be called by the arbitrator of the dispute, to declare the winning ruling. + * @param _externalDisputeID ID of the dispute in arbitrator contract. + * @param _ruling The ruling choice of the arbitration. + */ + function rule(uint256 _externalDisputeID, uint256 _ruling) external override { + uint256 localDisputeID = externalIDtoLocalID[_externalDisputeID]; + DisputeStruct storage dispute = disputes[localDisputeID]; + require(msg.sender == address(arbitrator), "Only the arbitrator can execute this."); + require(_ruling <= dispute.numberOfRulingOptions, "Invalid ruling."); + require(dispute.isRuled == false, "This dispute has been ruled already."); + + dispute.isRuled = true; + dispute.ruling = _ruling; + + emit Ruling(IArbitrator(msg.sender), _externalDisputeID, dispute.ruling); + } +} diff --git a/contracts/src/arbitration/CentralizedArbitrator.sol b/contracts/src/arbitration/CentralizedArbitrator.sol new file mode 100644 index 000000000..c67bdc6d8 --- /dev/null +++ b/contracts/src/arbitration/CentralizedArbitrator.sol @@ -0,0 +1,373 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8; + +import "./IArbitrator.sol"; + +/** @title Centralized Arbitrator + * @dev This is a centralized arbitrator deciding alone on the result of disputes. It allows to appeal the rulings by crowdfunding a desired choice. + * Note that normally the arbitrator should use a Dispute Kit contract which will define the algorithm for appeals/withdrawals. + * However to avoid complexity the code of the Dispute Kit is inlined within this contract. + * Also note that to avoid complexity funding the subset of choices is not possible in this contract. + */ +contract CentralizedArbitrator is IArbitrator { + /* Constants */ + + // The required fee stake that a party must pay depends on who won the previous round and is proportional to the appeal cost such that the fee stake for a round is stake multiplier * appeal cost for that round. + uint256 public constant WINNER_STAKE_MULTIPLIER = 10000; // Multiplier of the arbitration 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 arbitration cost that the loser has to pay as fee stake for a round in basis points. Default is 2x of appeal fee. + uint256 public constant LOSER_APPEAL_PERIOD_MULTIPLIER = 5000; // Multiplier of the appeal period for the choice that wasn't voted for in the previous round, in basis points. Default is 1/2 of original appeal period. + uint256 public constant MULTIPLIER_DIVISOR = 10000; + + uint256 constant NOT_PAYABLE_VALUE = 2**256 - 1; // High value to make sure that the appeal is too expensive. + + /* Structs */ + + struct DisputeStruct { + IArbitrable arbitrated; // The address of the arbitrable contract. + bytes arbitratorExtraData; // Extra data for the arbitrator. + uint256 choices; // The number of choices the arbitrator can choose from. + uint256 appealPeriodStart; // Time when the appeal funding becomes possible. + uint256 arbitrationFee; // Fee paid by the arbitrable for the arbitration. Must be equal or higher than arbitration cost. + uint256 ruling; // Ruling given by the arbitrator. + DisputeStatus status; // A current status of the dispute. + } + + struct Round { + mapping(uint256 => uint256) paidFees; // Tracks the fees paid for each choice in this round. + mapping(uint256 => bool) hasPaid; // True if this choice was fully funded, false otherwise. + mapping(address => mapping(uint256 => uint256)) contributions; // Maps contributors to their contributions for each choice. + uint256 feeRewards; // Sum of reimbursable appeal fees available to the parties that made contributions to the ruling that ultimately wins a dispute. + uint256[] fundedChoices; // Stores the choices that are fully funded. + } + + /* Storage */ + + address public owner = msg.sender; // Owner of the contract. + uint256 public appealDuration; // The duration of the appeal period. + + uint256 private arbitrationFee; // The cost to create a dispute. + uint256 private appealFee; // The cost to fund one of the choices. + + DisputeStruct[] public disputes; // Stores the dispute info. disputes[disputeID]. + mapping(uint256 => Round[]) public disputeIDtoRoundArray; // Maps dispute IDs to Round array that contains the info about crowdfunding. + + /* Events */ + + /** @dev Raised when a contribution is made, inside fundAppeal function. + * @param _disputeID ID of the dispute. + * @param _round The round the contribution was made to. + * @param _choice Indicates the choice option which got the contribution. + * @param _contributor Caller of fundAppeal function. + * @param _amount Contribution amount. + */ + event Contribution( + uint256 indexed _disputeID, + uint256 indexed _round, + uint256 _choice, + address indexed _contributor, + uint256 _amount + ); + + /** @dev Raised when a contributor withdraws a non-zero value. + * @param _disputeID ID of the dispute. + * @param _round The round the withdrawal was made from. + * @param _choice Indicates the choice which contributor gets rewards from. + * @param _contributor The beneficiary of the withdrawal. + * @param _amount Total withdrawn amount, consists of reimbursed deposits and rewards. + */ + event Withdrawal( + uint256 indexed _disputeID, + uint256 indexed _round, + uint256 _choice, + address indexed _contributor, + uint256 _amount + ); + + /** @dev To be raised when a choice is fully funded for appeal. + * @param _disputeID ID of the dispute. + * @param _round ID of the round where the choice was funded. + * @param _choice The choice that just got fully funded. + */ + event ChoiceFunded(uint256 indexed _disputeID, uint256 indexed _round, uint256 indexed _choice); + + /* Modifiers */ + + modifier onlyOwner() { + require(msg.sender == owner, "Can only be called by the owner."); + _; + } + + /** @dev Constructor. + * @param _arbitrationFee Amount to be paid for arbitration. + * @param _appealDuration Duration of the appeal period. + * @param _appealFee Amount to be paid to fund one of the appeal choices. + */ + constructor( + uint256 _arbitrationFee, + uint256 _appealDuration, + uint256 _appealFee + ) public { + arbitrationFee = _arbitrationFee; + appealDuration = _appealDuration; + appealFee = _appealFee; + } + + /* External and Public */ + + /** @dev Set the arbitration fee. Only callable by the owner. + * @param _arbitrationFee Amount to be paid for arbitration. + */ + function setArbitrationFee(uint256 _arbitrationFee) external onlyOwner { + arbitrationFee = _arbitrationFee; + } + + /** @dev Set the duration of the appeal period. Only callable by the owner. + * @param _appealDuration New duration of the appeal period. + */ + function setAppealDuration(uint256 _appealDuration) external onlyOwner { + appealDuration = _appealDuration; + } + + /** @dev Set the appeal fee. Only callable by the owner. + * @param _appealFee Amount to be paid for appeal. + */ + function setAppealFee(uint256 _appealFee) external onlyOwner { + appealFee = _appealFee; + } + + /** @dev Create a dispute. Must be called by the arbitrable contract. + * Must be paid at least arbitrationCost(). + * @param _choices Amount of choices the arbitrator can make in this dispute. + * @param _extraData Can be used to give additional info on the dispute to be created. + * @return disputeID ID of the dispute created. + */ + function createDispute(uint256 _choices, bytes calldata _extraData) + external + payable + override + returns (uint256 disputeID) + { + uint256 localArbitrationCost = arbitrationCost(_extraData); + require(msg.value >= localArbitrationCost, "Not enough ETH to cover arbitration costs."); + disputeID = disputes.length; + disputes.push( + DisputeStruct({ + arbitrated: IArbitrable(msg.sender), + arbitratorExtraData: _extraData, + choices: _choices, + appealPeriodStart: 0, + arbitrationFee: msg.value, + ruling: 0, + status: DisputeStatus.Waiting + }) + ); + + disputeIDtoRoundArray[disputeID].push(); + emit DisputeCreation(disputeID, IArbitrable(msg.sender)); + } + + /** @dev TRUSTED. Manages contributions, and appeals a dispute if at least two choices are fully funded. This function allows the appeals to be crowdfunded. + * Note that the surplus deposit will be reimbursed. + * @param _disputeID Index of the dispute to appeal. + * @param _choices Subset of choices that can be funded. Note that this contract only allows to fund one choice at a time. + */ + function fundAppeal(uint256 _disputeID, uint256[] calldata _choices) external payable override { + require(_choices.length == 1, "Can only fund 1 ruling."); + + DisputeStruct storage dispute = disputes[_disputeID]; + require(dispute.status == DisputeStatus.Appealable, "Dispute not appealable."); + + uint256 contributedChoice = _choices[0]; + require(contributedChoice <= dispute.choices, "There is no such ruling to fund."); + + (uint256 appealPeriodStart, uint256 appealPeriodEnd) = appealPeriod(_disputeID); + require(block.timestamp >= appealPeriodStart && block.timestamp < appealPeriodEnd, "Appeal period is over."); + + uint256 multiplier; + { + uint256 winner = currentRuling(_disputeID); + if (winner == contributedChoice) { + multiplier = WINNER_STAKE_MULTIPLIER; + } else { + require( + block.timestamp - appealPeriodStart < + ((appealPeriodEnd - appealPeriodStart) * LOSER_APPEAL_PERIOD_MULTIPLIER) / MULTIPLIER_DIVISOR, + "Appeal period is over for loser" + ); + multiplier = LOSER_STAKE_MULTIPLIER; + } + } + + Round[] storage rounds = disputeIDtoRoundArray[_disputeID]; + uint256 lastRoundIndex = rounds.length - 1; + Round storage lastRound = rounds[lastRoundIndex]; + require(!lastRound.hasPaid[contributedChoice], "Appeal fee is already paid."); + + uint256 localAppealCost = appealCost(_disputeID, dispute.arbitratorExtraData); + uint256 totalCost = localAppealCost + (localAppealCost * multiplier) / MULTIPLIER_DIVISOR; + + // Take up to the amount necessary to fund the current round at the current costs. + uint256 contribution; + if (totalCost > lastRound.paidFees[contributedChoice]) { + contribution = totalCost - lastRound.paidFees[contributedChoice] > msg.value // Overflows and underflows will be managed on the compiler level. + ? msg.value + : totalCost - lastRound.paidFees[contributedChoice]; + emit Contribution(_disputeID, lastRoundIndex, contributedChoice, msg.sender, contribution); + } + + lastRound.contributions[msg.sender][contributedChoice] += contribution; + lastRound.paidFees[contributedChoice] += contribution; + if (lastRound.paidFees[contributedChoice] >= totalCost) { + lastRound.feeRewards += lastRound.paidFees[contributedChoice]; + lastRound.fundedChoices.push(contributedChoice); + lastRound.hasPaid[contributedChoice] = true; + emit ChoiceFunded(_disputeID, lastRoundIndex, contributedChoice); + } + + if (lastRound.fundedChoices.length > 1) { + // At least two sides are fully funded. + rounds.push(); + lastRound.feeRewards = lastRound.feeRewards - localAppealCost; + + dispute.status = DisputeStatus.Waiting; + dispute.appealPeriodStart = 0; + emit AppealDecision(_disputeID, dispute.arbitrated); + } + + if (msg.value > contribution) payable(msg.sender).send(msg.value - contribution); + } + + /** @dev Give a ruling to a dispute. Once it's given the dispute can be appealed, and after the appeal period has passed this function should be called again to finalize the ruling. + * Accounts for the situation where the winner loses a case due to paying less appeal fees than expected. + * @param _disputeID ID of the dispute to rule. + * @param _ruling Ruling given by the arbitrator. Note that 0 means "Not able/wanting to make a decision". + */ + function giveRuling(uint256 _disputeID, uint256 _ruling) external onlyOwner { + DisputeStruct storage dispute = disputes[_disputeID]; + require(_ruling <= dispute.choices, "Invalid ruling."); + require(dispute.status != DisputeStatus.Solved, "The dispute must not be solved."); + + if (dispute.status == DisputeStatus.Waiting) { + dispute.ruling = _ruling; + dispute.status = DisputeStatus.Appealable; + dispute.appealPeriodStart = block.timestamp; + emit AppealPossible(_disputeID, dispute.arbitrated); + } else { + require(block.timestamp > dispute.appealPeriodStart + appealDuration, "Appeal period not passed yet."); + dispute.ruling = _ruling; + dispute.status = DisputeStatus.Solved; + + Round[] storage rounds = disputeIDtoRoundArray[_disputeID]; + Round storage lastRound = rounds[rounds.length - 1]; + // If only one ruling option is funded, it wins by default. Note that if any other ruling had funded, an appeal would have been created. + if (lastRound.fundedChoices.length == 1) { + dispute.ruling = lastRound.fundedChoices[0]; + } + + payable(msg.sender).send(dispute.arbitrationFee); // Avoid blocking. + dispute.arbitrated.rule(_disputeID, dispute.ruling); + } + } + + /** @dev Allows to withdraw any reimbursable fees or rewards after the dispute gets resolved. + * @param _disputeID Index of the dispute in disputes array. + * @param _beneficiary The address which rewards to withdraw. + * @param _round The round the caller wants to withdraw from. + * @param _choice The ruling option that the caller wants to withdraw from. + * @return amount The withdrawn amount. + */ + function withdrawFeesAndRewards( + uint256 _disputeID, + address payable _beneficiary, + uint256 _round, + uint256 _choice + ) external returns (uint256 amount) { + DisputeStruct storage dispute = disputes[_disputeID]; + require(dispute.status == DisputeStatus.Solved, "Dispute should be resolved."); + Round storage round = disputeIDtoRoundArray[_disputeID][_round]; + + 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 == dispute.ruling) { + // 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[dispute.ruling]) { + // 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); + } + } + + // ************************ // + // * Getters * // + // ************************ // + + /** @dev Cost of arbitration. + * @return fee The required amount. + */ + function arbitrationCost( + bytes memory /*_extraData*/ + ) public view override returns (uint256 fee) { + return arbitrationFee; + } + + /** @dev Cost of the appeal. + * @param _disputeID ID of the dispute to appeal. + * @return fee The amount to pay. Returns the unpayable value if the appeal is not possible. + */ + function appealCost( + uint256 _disputeID, + bytes memory /*_extraData */ + ) public view override returns (uint256 fee) { + DisputeStruct storage dispute = disputes[_disputeID]; + if (dispute.status == DisputeStatus.Appealable) { + return appealFee; + } else { + return NOT_PAYABLE_VALUE; + } + } + + /** @dev Compute the start and end of the dispute's appeal period, if possible. If the dispute is not appealble return (0, 0). + * @param _disputeID ID of the dispute. + * @return start The start of the period. + * @return end The end of the period. + */ + function appealPeriod(uint256 _disputeID) public view override returns (uint256 start, uint256 end) { + DisputeStruct storage dispute = disputes[_disputeID]; + if (dispute.status == DisputeStatus.Appealable) { + start = dispute.appealPeriodStart; + end = start + appealDuration; + } + return (start, end); + } + + /** @dev Return the status of a dispute. + * @param _disputeID ID of the dispute. + * @return status The status of the dispute. + */ + function disputeStatus(uint256 _disputeID) public view override returns (DisputeStatus status) { + return disputes[_disputeID].status; + } + + /** @dev Return the ruling of a dispute. + * @param _disputeID ID of the dispute. + * @return ruling The ruling which would or has been given. + */ + function currentRuling(uint256 _disputeID) public view override returns (uint256 ruling) { + return disputes[_disputeID].ruling; + } +} diff --git a/contracts/src/arbitration/IArbitrable.sol b/contracts/src/arbitration/IArbitrable.sol new file mode 100644 index 000000000..021169e0e --- /dev/null +++ b/contracts/src/arbitration/IArbitrable.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8; + +import "./IArbitrator.sol"; + +/** + * @title IArbitrable + * Arbitrable interface. Note that this interface follows the ERC-792 standard. + * When developing arbitrable contracts, we need to: + * - Define the action taken when a ruling is received by the contract. + * - Allow dispute creation. For this a function must call arbitrator.createDispute{value: _fee}(_choices,_extraData); + */ +interface IArbitrable { + /** + * @dev To be raised when a ruling is given. + * @param _arbitrator The arbitrator giving the ruling. + * @param _disputeID ID of the dispute in the Arbitrator contract. + * @param _ruling The ruling which was given. + */ + event Ruling(IArbitrator indexed _arbitrator, uint256 indexed _disputeID, uint256 _ruling); + + /** + * @dev Give a ruling for a dispute. Must be called by the arbitrator. + * The purpose of this function is to ensure that the address calling it has the right to rule on the contract. + * @param _disputeID ID of the dispute in the Arbitrator contract. + * @param _ruling Ruling given by the arbitrator. Note that 0 is reserved for "Not able/wanting to make a decision". + */ + function rule(uint256 _disputeID, uint256 _ruling) external; +} diff --git a/contracts/src/arbitration/IArbitrator.sol b/contracts/src/arbitration/IArbitrator.sol new file mode 100644 index 000000000..1077e07c4 --- /dev/null +++ b/contracts/src/arbitration/IArbitrator.sol @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8; + +import "./IArbitrable.sol"; + +/** + * @title Arbitrator + * Arbitrator interface for CourtV2. + * This interface for the most part follows the ERC-792 standard but also allows the appeal crowdfunding on arbitrator's side. + * When developing arbitrator contracts we need to: + * - Define the functions for dispute creation (createDispute). Don't forget to store the arbitrated contract and the disputeID (which should be unique, may nbDisputes). + * - Define the function for appeal crowdfunding (fundAppeal) in order to appeal the ruling. + * - Define the functions for cost display (arbitrationCost and appealCost). + * - Allow giving rulings. For this a function must call arbitrable.rule(disputeID, ruling). + */ +interface IArbitrator { + enum DisputeStatus { + Waiting, + Appealable, + Solved + } + + /** + * @dev To be emitted when a dispute is created. + * @param _disputeID ID of the dispute. + * @param _arbitrable The contract which created the dispute. + */ + event DisputeCreation(uint256 indexed _disputeID, IArbitrable indexed _arbitrable); + + /** + * @dev To be emitted when a dispute can be appealed. + * @param _disputeID ID of the dispute. + * @param _arbitrable The contract which created the dispute. + */ + event AppealPossible(uint256 indexed _disputeID, IArbitrable indexed _arbitrable); + + /** + * @dev To be emitted when the current ruling is appealed. + * @param _disputeID ID of the dispute. + * @param _arbitrable The contract which created the dispute. + */ + event AppealDecision(uint256 indexed _disputeID, IArbitrable indexed _arbitrable); + + /** + * @dev Create a dispute. Must be called by the arbitrable contract. + * Must pay at least arbitrationCost(_extraData). + * @param _choices Amount of choices the arbitrator can make in this dispute. + * @param _extraData Can be used to give additional info on the dispute to be created. + * @return disputeID ID of the dispute created. + */ + function createDispute(uint256 _choices, bytes calldata _extraData) external payable returns (uint256 disputeID); + + /** + * @dev Compute the cost of arbitration. It is recommended not to increase it often, as it can be highly time and gas consuming for the arbitrated contracts to cope with fee augmentation. + * @param _extraData Can be used to give additional info on the dispute to be created. + * @return cost Required cost of arbitration. + */ + function arbitrationCost(bytes memory _extraData) external view returns (uint256 cost); + + /** + * @dev Compute the cost of an appeal. + * @param _disputeID ID of the dispute to be appealed. + * @param _extraData Can be used to give additional info of the dispute. + * @return cost Required appeal cost. + */ + function appealCost(uint256 _disputeID, bytes memory _extraData) external view returns (uint256 cost); + + /** + * @dev Compute the start and end of the dispute's appeal period, if possible. If appeal is impossible: should return (0, 0). + * @param _disputeID ID of the dispute. + * @return start The start of the period. + * @return end The end of the period. + */ + function appealPeriod(uint256 _disputeID) external view returns (uint256 start, uint256 end); + + /** + * @dev Make a contribution to fund one (or the subset) of possible choices in order to appeal the ruling. + * Note that the desired appeal system will be defined by the selected Dispute Kit. + * @param _disputeID The ID of the dispute to appeal. + * @param _choices The choices to contribute to. + */ + function fundAppeal(uint256 _disputeID, uint256[] calldata _choices) external payable; + + /** + * @dev Return the status of a dispute. + * @param _disputeID ID of the dispute to rule. + * @return status The status of the dispute. + */ + function disputeStatus(uint256 _disputeID) external view returns (DisputeStatus status); + + /** + * @dev Return the current ruling of a dispute. This is useful for parties to know if they should appeal. + * @param _disputeID ID of the dispute. + * @return ruling The ruling which has been given or the one which will be given if there is no appeal. + */ + function currentRuling(uint256 _disputeID) external view returns (uint256 ruling); +} From f189dd906b97d3a3329b9825b823a0ddb97c3842 Mon Sep 17 00:00:00 2001 From: unknownunknown1 Date: Tue, 23 Nov 2021 19:06:02 +1000 Subject: [PATCH 2/6] fix(IArbitrator): replaced appealCost with fundingStatus --- CHANGELOG.md | 5 +- .../src/arbitration/CentralizedArbitrator.sol | 56 ++++++++++++------- contracts/src/arbitration/IArbitrator.sol | 20 ++++--- 3 files changed, 51 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 58a664272..332fadc41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ -## 0.1.0 (2021-11-17) +## 0.1.0 (2021-11-23) +- feat: modern toolchain setup and simple RNG smart contracts ([17f6a76](https://github.com/kleros/kleros-v2/commit/17f6a76)) +- feat(Arbitration): standard update ([ed930de](https://github.com/kleros/kleros-v2/commit/ed930de)) - chore: added GitHub code scanning ([4a70475](https://github.com/kleros/kleros-v2/commit/4a70475)) - chore: added the hardhat config for layer 2 networks, added hardhat-deploy and mocha ([a12ea0e](https://github.com/kleros/kleros-v2/commit/a12ea0e)) - test: added a test for IncrementalNG ([65a996b](https://github.com/kleros/kleros-v2/commit/65a996b)) @@ -7,4 +9,3 @@ - docs: license file added ([cb62d2c](https://github.com/kleros/kleros-v2/commit/cb62d2c)) - docs: readme and spdx headers ([8a5b397](https://github.com/kleros/kleros-v2/commit/8a5b397)) - docs: updated ([5b9a8f1](https://github.com/kleros/kleros-v2/commit/5b9a8f1)) -- feat: modern toolchain setup and simple RNG smart contracts ([17f6a76](https://github.com/kleros/kleros-v2/commit/17f6a76)) diff --git a/contracts/src/arbitration/CentralizedArbitrator.sol b/contracts/src/arbitration/CentralizedArbitrator.sol index c67bdc6d8..6c28916a9 100644 --- a/contracts/src/arbitration/CentralizedArbitrator.sol +++ b/contracts/src/arbitration/CentralizedArbitrator.sol @@ -14,13 +14,11 @@ contract CentralizedArbitrator is IArbitrator { /* Constants */ // The required fee stake that a party must pay depends on who won the previous round and is proportional to the appeal cost such that the fee stake for a round is stake multiplier * appeal cost for that round. - uint256 public constant WINNER_STAKE_MULTIPLIER = 10000; // Multiplier of the arbitration 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 arbitration 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 WINNER_STAKE_MULTIPLIER = 10000; // Multiplier of the appeal cost that the winner has to pay as fee stake for a round in basis points. Default is 1x of appeal fee. + uint256 public constant LOSER_STAKE_MULTIPLIER = 20000; // Multiplier of the appeal cost that the loser has to pay as fee stake for a round in basis points. Default is 2x of appeal fee. uint256 public constant LOSER_APPEAL_PERIOD_MULTIPLIER = 5000; // Multiplier of the appeal period for the choice that wasn't voted for in the previous round, in basis points. Default is 1/2 of original appeal period. uint256 public constant MULTIPLIER_DIVISOR = 10000; - uint256 constant NOT_PAYABLE_VALUE = 2**256 - 1; // High value to make sure that the appeal is too expensive. - /* Structs */ struct DisputeStruct { @@ -46,8 +44,8 @@ contract CentralizedArbitrator is IArbitrator { address public owner = msg.sender; // Owner of the contract. uint256 public appealDuration; // The duration of the appeal period. - uint256 private arbitrationFee; // The cost to create a dispute. - uint256 private appealFee; // The cost to fund one of the choices. + uint256 private arbitrationFee; // The cost to create a dispute. Made private because of the arbitrationCost() getter. + uint256 public appealFee; // The cost to fund one of the choices, not counting the additional fee stake amount. DisputeStruct[] public disputes; // Stores the dispute info. disputes[disputeID]. mapping(uint256 => Round[]) public disputeIDtoRoundArray; // Maps dispute IDs to Round array that contains the info about crowdfunding. @@ -101,7 +99,7 @@ contract CentralizedArbitrator is IArbitrator { /** @dev Constructor. * @param _arbitrationFee Amount to be paid for arbitration. * @param _appealDuration Duration of the appeal period. - * @param _appealFee Amount to be paid to fund one of the appeal choices. + * @param _appealFee Amount to be paid to fund one of the appeal choices, not counting the additional fee stake amount. */ constructor( uint256 _arbitrationFee, @@ -204,8 +202,7 @@ contract CentralizedArbitrator is IArbitrator { Round storage lastRound = rounds[lastRoundIndex]; require(!lastRound.hasPaid[contributedChoice], "Appeal fee is already paid."); - uint256 localAppealCost = appealCost(_disputeID, dispute.arbitratorExtraData); - uint256 totalCost = localAppealCost + (localAppealCost * multiplier) / MULTIPLIER_DIVISOR; + uint256 totalCost = appealFee + (appealFee * multiplier) / MULTIPLIER_DIVISOR; // Take up to the amount necessary to fund the current round at the current costs. uint256 contribution; @@ -228,7 +225,7 @@ contract CentralizedArbitrator is IArbitrator { if (lastRound.fundedChoices.length > 1) { // At least two sides are fully funded. rounds.push(); - lastRound.feeRewards = lastRound.feeRewards - localAppealCost; + lastRound.feeRewards = lastRound.feeRewards - appealFee; dispute.status = DisputeStatus.Waiting; dispute.appealPeriodStart = 0; @@ -325,20 +322,39 @@ contract CentralizedArbitrator is IArbitrator { return arbitrationFee; } - /** @dev Cost of the appeal. - * @param _disputeID ID of the dispute to appeal. - * @return fee The amount to pay. Returns the unpayable value if the appeal is not possible. + /** @dev Return the funded amount and funding goal for one (or the subset) of choices. + * @param _disputeID The ID of the dispute to appeal. + * @param _choices The one (or the subset) of choices to check the funding status of. + * @return funded The amount funded so far for this subset in wei. + * @return goal The amount to fully fund this subset in wei. */ - function appealCost( - uint256 _disputeID, - bytes memory /*_extraData */ - ) public view override returns (uint256 fee) { + function fundingStatus(uint256 _disputeID, uint256[] memory _choices) + external + view + override + returns (uint256 funded, uint256 goal) + { + require(_choices.length == 1, "Can only fund 1 ruling."); + uint256 contributedChoice = _choices[0]; + DisputeStruct storage dispute = disputes[_disputeID]; - if (dispute.status == DisputeStatus.Appealable) { - return appealFee; + require(contributedChoice <= dispute.choices, "There is no such ruling to fund."); + require(dispute.status == DisputeStatus.Appealable, "Dispute not appealable."); + + uint256 multiplier; + uint256 winner = currentRuling(_disputeID); + if (winner == contributedChoice) { + multiplier = WINNER_STAKE_MULTIPLIER; } else { - return NOT_PAYABLE_VALUE; + multiplier = LOSER_STAKE_MULTIPLIER; } + + goal = appealFee + (appealFee * multiplier) / MULTIPLIER_DIVISOR; + + Round[] storage rounds = disputeIDtoRoundArray[_disputeID]; + Round storage lastRound = rounds[rounds.length - 1]; + + return (lastRound.paidFees[contributedChoice], goal); } /** @dev Compute the start and end of the dispute's appeal period, if possible. If the dispute is not appealble return (0, 0). diff --git a/contracts/src/arbitration/IArbitrator.sol b/contracts/src/arbitration/IArbitrator.sol index 1077e07c4..fbb11ab9d 100644 --- a/contracts/src/arbitration/IArbitrator.sol +++ b/contracts/src/arbitration/IArbitrator.sol @@ -7,11 +7,11 @@ import "./IArbitrable.sol"; /** * @title Arbitrator * Arbitrator interface for CourtV2. - * This interface for the most part follows the ERC-792 standard but also allows the appeal crowdfunding on arbitrator's side. + * This interface follows the ERC-792 standard but also allows the appeal crowdfunding on arbitrator's side. * When developing arbitrator contracts we need to: * - Define the functions for dispute creation (createDispute). Don't forget to store the arbitrated contract and the disputeID (which should be unique, may nbDisputes). * - Define the function for appeal crowdfunding (fundAppeal) in order to appeal the ruling. - * - Define the functions for cost display (arbitrationCost and appealCost). + * - Define the functions for cost display (arbitrationCost and fundingStatus). * - Allow giving rulings. For this a function must call arbitrable.rule(disputeID, ruling). */ interface IArbitrator { @@ -59,12 +59,17 @@ interface IArbitrator { function arbitrationCost(bytes memory _extraData) external view returns (uint256 cost); /** - * @dev Compute the cost of an appeal. - * @param _disputeID ID of the dispute to be appealed. - * @param _extraData Can be used to give additional info of the dispute. - * @return cost Required appeal cost. + * @dev Return the funded amount and funding goal for one (or the subset) of choices. + * Note that the status info may not be available if the dispute is not currently appealable. + * @param _disputeID The ID of the dispute to appeal. + * @param _choices The one (or the subset) of choices to check the funding status of. + * @return funded The amount funded so far for this subset in wei. + * @return goal The amount to fully fund this subset in wei. */ - function appealCost(uint256 _disputeID, bytes memory _extraData) external view returns (uint256 cost); + function fundingStatus(uint256 _disputeID, uint256[] memory _choices) + external + view + returns (uint256 funded, uint256 goal); /** * @dev Compute the start and end of the dispute's appeal period, if possible. If appeal is impossible: should return (0, 0). @@ -76,7 +81,6 @@ interface IArbitrator { /** * @dev Make a contribution to fund one (or the subset) of possible choices in order to appeal the ruling. - * Note that the desired appeal system will be defined by the selected Dispute Kit. * @param _disputeID The ID of the dispute to appeal. * @param _choices The choices to contribute to. */ From 4770b1f74547ecbbbd3abd1db2c2154aa10623e6 Mon Sep 17 00:00:00 2001 From: unknownunknown1 Date: Tue, 23 Nov 2021 21:16:13 +1000 Subject: [PATCH 3/6] fix(Arbitrator): memory to calldata --- CHANGELOG.md | 1 + contracts/src/arbitration/CentralizedArbitrator.sol | 4 ++-- contracts/src/arbitration/IArbitrator.sol | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 332fadc41..e9ee4980c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## 0.1.0 (2021-11-23) +- fix(IArbitrator): replaced appealCost with fundingStatus ([f189dd9](https://github.com/kleros/kleros-v2/commit/f189dd9)) - feat: modern toolchain setup and simple RNG smart contracts ([17f6a76](https://github.com/kleros/kleros-v2/commit/17f6a76)) - feat(Arbitration): standard update ([ed930de](https://github.com/kleros/kleros-v2/commit/ed930de)) - chore: added GitHub code scanning ([4a70475](https://github.com/kleros/kleros-v2/commit/4a70475)) diff --git a/contracts/src/arbitration/CentralizedArbitrator.sol b/contracts/src/arbitration/CentralizedArbitrator.sol index 6c28916a9..6ca7d1ae0 100644 --- a/contracts/src/arbitration/CentralizedArbitrator.sol +++ b/contracts/src/arbitration/CentralizedArbitrator.sol @@ -317,7 +317,7 @@ contract CentralizedArbitrator is IArbitrator { * @return fee The required amount. */ function arbitrationCost( - bytes memory /*_extraData*/ + bytes calldata /*_extraData*/ ) public view override returns (uint256 fee) { return arbitrationFee; } @@ -328,7 +328,7 @@ contract CentralizedArbitrator is IArbitrator { * @return funded The amount funded so far for this subset in wei. * @return goal The amount to fully fund this subset in wei. */ - function fundingStatus(uint256 _disputeID, uint256[] memory _choices) + function fundingStatus(uint256 _disputeID, uint256[] calldata _choices) external view override diff --git a/contracts/src/arbitration/IArbitrator.sol b/contracts/src/arbitration/IArbitrator.sol index fbb11ab9d..16bd403aa 100644 --- a/contracts/src/arbitration/IArbitrator.sol +++ b/contracts/src/arbitration/IArbitrator.sol @@ -56,7 +56,7 @@ interface IArbitrator { * @param _extraData Can be used to give additional info on the dispute to be created. * @return cost Required cost of arbitration. */ - function arbitrationCost(bytes memory _extraData) external view returns (uint256 cost); + function arbitrationCost(bytes calldata _extraData) external view returns (uint256 cost); /** * @dev Return the funded amount and funding goal for one (or the subset) of choices. @@ -66,7 +66,7 @@ interface IArbitrator { * @return funded The amount funded so far for this subset in wei. * @return goal The amount to fully fund this subset in wei. */ - function fundingStatus(uint256 _disputeID, uint256[] memory _choices) + function fundingStatus(uint256 _disputeID, uint256[] calldata _choices) external view returns (uint256 funded, uint256 goal); From 02c20ce6807edc979ec6b3d03083fb5c48efbd2d Mon Sep 17 00:00:00 2001 From: unknownunknown1 Date: Thu, 25 Nov 2021 20:24:17 +1000 Subject: [PATCH 4/6] fix(IArbitrator): appeals removed from the standard --- CHANGELOG.md | 3 +- .../src/arbitration/CentralizedArbitrator.sol | 80 ++++++++++--------- contracts/src/arbitration/IArbitrator.sol | 47 +---------- 3 files changed, 45 insertions(+), 85 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e9ee4980c..1a57d246b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ -## 0.1.0 (2021-11-23) +## 0.1.0 (2021-11-25) +- fix(Arbitrator): memory to calldata ([4770b1f](https://github.com/kleros/kleros-v2/commit/4770b1f)) - fix(IArbitrator): replaced appealCost with fundingStatus ([f189dd9](https://github.com/kleros/kleros-v2/commit/f189dd9)) - feat: modern toolchain setup and simple RNG smart contracts ([17f6a76](https://github.com/kleros/kleros-v2/commit/17f6a76)) - feat(Arbitration): standard update ([ed930de](https://github.com/kleros/kleros-v2/commit/ed930de)) diff --git a/contracts/src/arbitration/CentralizedArbitrator.sol b/contracts/src/arbitration/CentralizedArbitrator.sol index 6ca7d1ae0..f63386020 100644 --- a/contracts/src/arbitration/CentralizedArbitrator.sol +++ b/contracts/src/arbitration/CentralizedArbitrator.sol @@ -5,10 +5,10 @@ pragma solidity ^0.8; import "./IArbitrator.sol"; /** @title Centralized Arbitrator - * @dev This is a centralized arbitrator deciding alone on the result of disputes. It allows to appeal the rulings by crowdfunding a desired choice. + * @dev This is a centralized arbitrator deciding alone on the result of disputes. It illustrates how the appeals can be handled on the arbitrator level. + * In this particular contract the rulings can be appealed by crowdfunding a desired choice. * Note that normally the arbitrator should use a Dispute Kit contract which will define the algorithm for appeals/withdrawals. * However to avoid complexity the code of the Dispute Kit is inlined within this contract. - * Also note that to avoid complexity funding the subset of choices is not possible in this contract. */ contract CentralizedArbitrator is IArbitrator { /* Constants */ @@ -52,6 +52,20 @@ contract CentralizedArbitrator is IArbitrator { /* Events */ + /** + * @dev To be emitted when a dispute can be appealed. + * @param _disputeID ID of the dispute. + * @param _arbitrable The contract which created the dispute. + */ + event AppealPossible(uint256 indexed _disputeID, IArbitrable indexed _arbitrable); + + /** + * @dev To be emitted when the current ruling is appealed. + * @param _disputeID ID of the dispute. + * @param _arbitrable The contract which created the dispute. + */ + event AppealDecision(uint256 indexed _disputeID, IArbitrable indexed _arbitrable); + /** @dev Raised when a contribution is made, inside fundAppeal function. * @param _disputeID ID of the dispute. * @param _round The round the contribution was made to. @@ -168,16 +182,12 @@ contract CentralizedArbitrator is IArbitrator { /** @dev TRUSTED. Manages contributions, and appeals a dispute if at least two choices are fully funded. This function allows the appeals to be crowdfunded. * Note that the surplus deposit will be reimbursed. * @param _disputeID Index of the dispute to appeal. - * @param _choices Subset of choices that can be funded. Note that this contract only allows to fund one choice at a time. + * @param _choice A choice that receives funding. */ - function fundAppeal(uint256 _disputeID, uint256[] calldata _choices) external payable override { - require(_choices.length == 1, "Can only fund 1 ruling."); - + function fundAppeal(uint256 _disputeID, uint256 _choice) external payable { DisputeStruct storage dispute = disputes[_disputeID]; require(dispute.status == DisputeStatus.Appealable, "Dispute not appealable."); - - uint256 contributedChoice = _choices[0]; - require(contributedChoice <= dispute.choices, "There is no such ruling to fund."); + require(_choice <= dispute.choices, "There is no such ruling to fund."); (uint256 appealPeriodStart, uint256 appealPeriodEnd) = appealPeriod(_disputeID); require(block.timestamp >= appealPeriodStart && block.timestamp < appealPeriodEnd, "Appeal period is over."); @@ -185,7 +195,7 @@ contract CentralizedArbitrator is IArbitrator { uint256 multiplier; { uint256 winner = currentRuling(_disputeID); - if (winner == contributedChoice) { + if (winner == _choice) { multiplier = WINNER_STAKE_MULTIPLIER; } else { require( @@ -200,26 +210,26 @@ contract CentralizedArbitrator is IArbitrator { Round[] storage rounds = disputeIDtoRoundArray[_disputeID]; uint256 lastRoundIndex = rounds.length - 1; Round storage lastRound = rounds[lastRoundIndex]; - require(!lastRound.hasPaid[contributedChoice], "Appeal fee is already paid."); + require(!lastRound.hasPaid[_choice], "Appeal fee is already paid."); uint256 totalCost = appealFee + (appealFee * multiplier) / MULTIPLIER_DIVISOR; // Take up to the amount necessary to fund the current round at the current costs. uint256 contribution; - if (totalCost > lastRound.paidFees[contributedChoice]) { - contribution = totalCost - lastRound.paidFees[contributedChoice] > msg.value // Overflows and underflows will be managed on the compiler level. + if (totalCost > lastRound.paidFees[_choice]) { + contribution = totalCost - lastRound.paidFees[_choice] > msg.value // Overflows and underflows will be managed on the compiler level. ? msg.value - : totalCost - lastRound.paidFees[contributedChoice]; - emit Contribution(_disputeID, lastRoundIndex, contributedChoice, msg.sender, contribution); + : totalCost - lastRound.paidFees[_choice]; + emit Contribution(_disputeID, lastRoundIndex, _choice, msg.sender, contribution); } - lastRound.contributions[msg.sender][contributedChoice] += contribution; - lastRound.paidFees[contributedChoice] += contribution; - if (lastRound.paidFees[contributedChoice] >= totalCost) { - lastRound.feeRewards += lastRound.paidFees[contributedChoice]; - lastRound.fundedChoices.push(contributedChoice); - lastRound.hasPaid[contributedChoice] = true; - emit ChoiceFunded(_disputeID, lastRoundIndex, contributedChoice); + lastRound.contributions[msg.sender][_choice] += contribution; + lastRound.paidFees[_choice] += contribution; + if (lastRound.paidFees[_choice] >= totalCost) { + lastRound.feeRewards += lastRound.paidFees[_choice]; + lastRound.fundedChoices.push(_choice); + lastRound.hasPaid[_choice] = true; + emit ChoiceFunded(_disputeID, lastRoundIndex, _choice); } if (lastRound.fundedChoices.length > 1) { @@ -322,28 +332,20 @@ contract CentralizedArbitrator is IArbitrator { return arbitrationFee; } - /** @dev Return the funded amount and funding goal for one (or the subset) of choices. + /** @dev Return the funded amount and funding goal for one of the choices. * @param _disputeID The ID of the dispute to appeal. - * @param _choices The one (or the subset) of choices to check the funding status of. - * @return funded The amount funded so far for this subset in wei. - * @return goal The amount to fully fund this subset in wei. + * @param _choice The choice to check the funding status of. + * @return funded The amount funded so far for this choice in wei. + * @return goal The amount to fully fund this choice in wei. */ - function fundingStatus(uint256 _disputeID, uint256[] calldata _choices) - external - view - override - returns (uint256 funded, uint256 goal) - { - require(_choices.length == 1, "Can only fund 1 ruling."); - uint256 contributedChoice = _choices[0]; - + function fundingStatus(uint256 _disputeID, uint256 _choice) external view returns (uint256 funded, uint256 goal) { DisputeStruct storage dispute = disputes[_disputeID]; - require(contributedChoice <= dispute.choices, "There is no such ruling to fund."); + require(_choice <= dispute.choices, "There is no such ruling to fund."); require(dispute.status == DisputeStatus.Appealable, "Dispute not appealable."); uint256 multiplier; uint256 winner = currentRuling(_disputeID); - if (winner == contributedChoice) { + if (winner == _choice) { multiplier = WINNER_STAKE_MULTIPLIER; } else { multiplier = LOSER_STAKE_MULTIPLIER; @@ -354,7 +356,7 @@ contract CentralizedArbitrator is IArbitrator { Round[] storage rounds = disputeIDtoRoundArray[_disputeID]; Round storage lastRound = rounds[rounds.length - 1]; - return (lastRound.paidFees[contributedChoice], goal); + return (lastRound.paidFees[_choice], goal); } /** @dev Compute the start and end of the dispute's appeal period, if possible. If the dispute is not appealble return (0, 0). @@ -362,7 +364,7 @@ contract CentralizedArbitrator is IArbitrator { * @return start The start of the period. * @return end The end of the period. */ - function appealPeriod(uint256 _disputeID) public view override returns (uint256 start, uint256 end) { + function appealPeriod(uint256 _disputeID) public view returns (uint256 start, uint256 end) { DisputeStruct storage dispute = disputes[_disputeID]; if (dispute.status == DisputeStatus.Appealable) { start = dispute.appealPeriodStart; diff --git a/contracts/src/arbitration/IArbitrator.sol b/contracts/src/arbitration/IArbitrator.sol index 16bd403aa..55fc07621 100644 --- a/contracts/src/arbitration/IArbitrator.sol +++ b/contracts/src/arbitration/IArbitrator.sol @@ -7,11 +7,10 @@ import "./IArbitrable.sol"; /** * @title Arbitrator * Arbitrator interface for CourtV2. - * This interface follows the ERC-792 standard but also allows the appeal crowdfunding on arbitrator's side. + * Unlike the ERC-792 this standard doesn't have anything related to appeals, so each arbitrator can implement an appeal system that suits it the most. * When developing arbitrator contracts we need to: * - Define the functions for dispute creation (createDispute). Don't forget to store the arbitrated contract and the disputeID (which should be unique, may nbDisputes). - * - Define the function for appeal crowdfunding (fundAppeal) in order to appeal the ruling. - * - Define the functions for cost display (arbitrationCost and fundingStatus). + * - Define the functions for cost display (arbitrationCost). * - Allow giving rulings. For this a function must call arbitrable.rule(disputeID, ruling). */ interface IArbitrator { @@ -28,20 +27,6 @@ interface IArbitrator { */ event DisputeCreation(uint256 indexed _disputeID, IArbitrable indexed _arbitrable); - /** - * @dev To be emitted when a dispute can be appealed. - * @param _disputeID ID of the dispute. - * @param _arbitrable The contract which created the dispute. - */ - event AppealPossible(uint256 indexed _disputeID, IArbitrable indexed _arbitrable); - - /** - * @dev To be emitted when the current ruling is appealed. - * @param _disputeID ID of the dispute. - * @param _arbitrable The contract which created the dispute. - */ - event AppealDecision(uint256 indexed _disputeID, IArbitrable indexed _arbitrable); - /** * @dev Create a dispute. Must be called by the arbitrable contract. * Must pay at least arbitrationCost(_extraData). @@ -58,34 +43,6 @@ interface IArbitrator { */ function arbitrationCost(bytes calldata _extraData) external view returns (uint256 cost); - /** - * @dev Return the funded amount and funding goal for one (or the subset) of choices. - * Note that the status info may not be available if the dispute is not currently appealable. - * @param _disputeID The ID of the dispute to appeal. - * @param _choices The one (or the subset) of choices to check the funding status of. - * @return funded The amount funded so far for this subset in wei. - * @return goal The amount to fully fund this subset in wei. - */ - function fundingStatus(uint256 _disputeID, uint256[] calldata _choices) - external - view - returns (uint256 funded, uint256 goal); - - /** - * @dev Compute the start and end of the dispute's appeal period, if possible. If appeal is impossible: should return (0, 0). - * @param _disputeID ID of the dispute. - * @return start The start of the period. - * @return end The end of the period. - */ - function appealPeriod(uint256 _disputeID) external view returns (uint256 start, uint256 end); - - /** - * @dev Make a contribution to fund one (or the subset) of possible choices in order to appeal the ruling. - * @param _disputeID The ID of the dispute to appeal. - * @param _choices The choices to contribute to. - */ - function fundAppeal(uint256 _disputeID, uint256[] calldata _choices) external payable; - /** * @dev Return the status of a dispute. * @param _disputeID ID of the dispute to rule. From e81fb8b6434d46849ce6da47157789e2e820eb8f Mon Sep 17 00:00:00 2001 From: unknownunknown1 Date: Wed, 1 Dec 2021 02:18:16 +1000 Subject: [PATCH 5/6] fix(IArbitrator): interface simplification --- CHANGELOG.md | 3 +- .../src/arbitration/ArbitrableExample.sol | 2 +- .../src/arbitration/CentralizedArbitrator.sol | 73 +++++++------------ contracts/src/arbitration/IArbitrator.sol | 28 +------ 4 files changed, 35 insertions(+), 71 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a57d246b..2948d8743 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ -## 0.1.0 (2021-11-25) +## 0.1.0 (2021-11-30) - fix(Arbitrator): memory to calldata ([4770b1f](https://github.com/kleros/kleros-v2/commit/4770b1f)) +- fix(IArbitrator): appeals removed from the standard ([02c20ce](https://github.com/kleros/kleros-v2/commit/02c20ce)) - fix(IArbitrator): replaced appealCost with fundingStatus ([f189dd9](https://github.com/kleros/kleros-v2/commit/f189dd9)) - feat: modern toolchain setup and simple RNG smart contracts ([17f6a76](https://github.com/kleros/kleros-v2/commit/17f6a76)) - feat(Arbitration): standard update ([ed930de](https://github.com/kleros/kleros-v2/commit/ed930de)) diff --git a/contracts/src/arbitration/ArbitrableExample.sol b/contracts/src/arbitration/ArbitrableExample.sol index 328ea3b13..757f3bb89 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 an arbitrable contract which connects to the arbitator that implements the updated interface. + * An example of the arbitrable contract which connects to the arbitator that implements IArbitrator interface. */ contract ArbitrableExample is IArbitrable { struct DisputeStruct { diff --git a/contracts/src/arbitration/CentralizedArbitrator.sol b/contracts/src/arbitration/CentralizedArbitrator.sol index f63386020..cfc0bf4a1 100644 --- a/contracts/src/arbitration/CentralizedArbitrator.sol +++ b/contracts/src/arbitration/CentralizedArbitrator.sol @@ -5,10 +5,8 @@ pragma solidity ^0.8; import "./IArbitrator.sol"; /** @title Centralized Arbitrator - * @dev This is a centralized arbitrator deciding alone on the result of disputes. It illustrates how the appeals can be handled on the arbitrator level. - * In this particular contract the rulings can be appealed by crowdfunding a desired choice. - * Note that normally the arbitrator should use a Dispute Kit contract which will define the algorithm for appeals/withdrawals. - * However to avoid complexity the code of the Dispute Kit is inlined within this contract. + * @dev This is a centralized arbitrator deciding alone on the result of disputes. It illustrates how IArbitrator interface can be implemented. + * Note that this contract supports appeals. The ruling given by the arbitrator can be appealed by crowdfunding a desired choice. */ contract CentralizedArbitrator is IArbitrator { /* Constants */ @@ -19,6 +17,14 @@ contract CentralizedArbitrator is IArbitrator { uint256 public constant LOSER_APPEAL_PERIOD_MULTIPLIER = 5000; // Multiplier of the appeal period for the choice that wasn't voted for in the previous round, in basis points. Default is 1/2 of original appeal period. uint256 public constant MULTIPLIER_DIVISOR = 10000; + /* Enums */ + + enum DisputeStatus { + Waiting, // The dispute is waiting for the ruling or not created. + Appealable, // The dispute can be appealed. + Solved // The dispute is resolved. + } + /* Structs */ struct DisputeStruct { @@ -44,7 +50,7 @@ contract CentralizedArbitrator is IArbitrator { address public owner = msg.sender; // Owner of the contract. uint256 public appealDuration; // The duration of the appeal period. - uint256 private arbitrationFee; // The cost to create a dispute. Made private because of the arbitrationCost() getter. + uint256 private arbitrationFee; // The cost to create a dispute. Made private because of the cost() getter. uint256 public appealFee; // The cost to fund one of the choices, not counting the additional fee stake amount. DisputeStruct[] public disputes; // Stores the dispute info. disputes[disputeID]. @@ -149,7 +155,7 @@ contract CentralizedArbitrator is IArbitrator { } /** @dev Create a dispute. Must be called by the arbitrable contract. - * Must be paid at least arbitrationCost(). + * Must be paid at least cost(). * @param _choices Amount of choices the arbitrator can make in this dispute. * @param _extraData Can be used to give additional info on the dispute to be created. * @return disputeID ID of the dispute created. @@ -160,8 +166,8 @@ contract CentralizedArbitrator is IArbitrator { override returns (uint256 disputeID) { - uint256 localArbitrationCost = arbitrationCost(_extraData); - require(msg.value >= localArbitrationCost, "Not enough ETH to cover arbitration costs."); + uint256 arbitrationCost = cost(_extraData); + require(msg.value >= arbitrationCost, "Not enough ETH to cover arbitration costs."); disputeID = disputes.length; disputes.push( DisputeStruct({ @@ -193,18 +199,15 @@ contract CentralizedArbitrator is IArbitrator { require(block.timestamp >= appealPeriodStart && block.timestamp < appealPeriodEnd, "Appeal period is over."); uint256 multiplier; - { - uint256 winner = currentRuling(_disputeID); - if (winner == _choice) { - multiplier = WINNER_STAKE_MULTIPLIER; - } else { - require( - block.timestamp - appealPeriodStart < - ((appealPeriodEnd - appealPeriodStart) * LOSER_APPEAL_PERIOD_MULTIPLIER) / MULTIPLIER_DIVISOR, - "Appeal period is over for loser" - ); - multiplier = LOSER_STAKE_MULTIPLIER; - } + if (dispute.ruling == _choice) { + multiplier = WINNER_STAKE_MULTIPLIER; + } else { + require( + block.timestamp - appealPeriodStart < + ((appealPeriodEnd - appealPeriodStart) * LOSER_APPEAL_PERIOD_MULTIPLIER) / MULTIPLIER_DIVISOR, + "Appeal period is over for loser" + ); + multiplier = LOSER_STAKE_MULTIPLIER; } Round[] storage rounds = disputeIDtoRoundArray[_disputeID]; @@ -248,7 +251,7 @@ contract CentralizedArbitrator is IArbitrator { /** @dev Give a ruling to a dispute. Once it's given the dispute can be appealed, and after the appeal period has passed this function should be called again to finalize the ruling. * Accounts for the situation where the winner loses a case due to paying less appeal fees than expected. * @param _disputeID ID of the dispute to rule. - * @param _ruling Ruling given by the arbitrator. Note that 0 means "Not able/wanting to make a decision". + * @param _ruling Ruling given by the arbitrator. Note that 0 means that arbitrator chose "Refused to rule". */ function giveRuling(uint256 _disputeID, uint256 _ruling) external onlyOwner { DisputeStruct storage dispute = disputes[_disputeID]; @@ -326,7 +329,7 @@ contract CentralizedArbitrator is IArbitrator { /** @dev Cost of arbitration. * @return fee The required amount. */ - function arbitrationCost( + function cost( bytes calldata /*_extraData*/ ) public view override returns (uint256 fee) { return arbitrationFee; @@ -343,16 +346,12 @@ contract CentralizedArbitrator is IArbitrator { require(_choice <= dispute.choices, "There is no such ruling to fund."); require(dispute.status == DisputeStatus.Appealable, "Dispute not appealable."); - uint256 multiplier; - uint256 winner = currentRuling(_disputeID); - if (winner == _choice) { - multiplier = WINNER_STAKE_MULTIPLIER; + if (dispute.ruling == _choice) { + goal = appealFee + (appealFee * WINNER_STAKE_MULTIPLIER) / MULTIPLIER_DIVISOR; } else { - multiplier = LOSER_STAKE_MULTIPLIER; + goal = appealFee + (appealFee * LOSER_STAKE_MULTIPLIER) / MULTIPLIER_DIVISOR; } - goal = appealFee + (appealFee * multiplier) / MULTIPLIER_DIVISOR; - Round[] storage rounds = disputeIDtoRoundArray[_disputeID]; Round storage lastRound = rounds[rounds.length - 1]; @@ -372,20 +371,4 @@ contract CentralizedArbitrator is IArbitrator { } return (start, end); } - - /** @dev Return the status of a dispute. - * @param _disputeID ID of the dispute. - * @return status The status of the dispute. - */ - function disputeStatus(uint256 _disputeID) public view override returns (DisputeStatus status) { - return disputes[_disputeID].status; - } - - /** @dev Return the ruling of a dispute. - * @param _disputeID ID of the dispute. - * @return ruling The ruling which would or has been given. - */ - function currentRuling(uint256 _disputeID) public view override returns (uint256 ruling) { - return disputes[_disputeID].ruling; - } } diff --git a/contracts/src/arbitration/IArbitrator.sol b/contracts/src/arbitration/IArbitrator.sol index 55fc07621..d63a5371f 100644 --- a/contracts/src/arbitration/IArbitrator.sol +++ b/contracts/src/arbitration/IArbitrator.sol @@ -6,20 +6,14 @@ import "./IArbitrable.sol"; /** * @title Arbitrator - * Arbitrator interface for CourtV2. + * Arbitrator interface that implements the new arbitration standard. * Unlike the ERC-792 this standard doesn't have anything related to appeals, so each arbitrator can implement an appeal system that suits it the most. * When developing arbitrator contracts we need to: * - Define the functions for dispute creation (createDispute). Don't forget to store the arbitrated contract and the disputeID (which should be unique, may nbDisputes). - * - Define the functions for cost display (arbitrationCost). + * - Define the functions for cost display (cost). * - Allow giving rulings. For this a function must call arbitrable.rule(disputeID, ruling). */ interface IArbitrator { - enum DisputeStatus { - Waiting, - Appealable, - Solved - } - /** * @dev To be emitted when a dispute is created. * @param _disputeID ID of the dispute. @@ -29,7 +23,7 @@ interface IArbitrator { /** * @dev Create a dispute. Must be called by the arbitrable contract. - * Must pay at least arbitrationCost(_extraData). + * Must pay at least cost(_extraData). * @param _choices Amount of choices the arbitrator can make in this dispute. * @param _extraData Can be used to give additional info on the dispute to be created. * @return disputeID ID of the dispute created. @@ -41,19 +35,5 @@ interface IArbitrator { * @param _extraData Can be used to give additional info on the dispute to be created. * @return cost Required cost of arbitration. */ - function arbitrationCost(bytes calldata _extraData) external view returns (uint256 cost); - - /** - * @dev Return the status of a dispute. - * @param _disputeID ID of the dispute to rule. - * @return status The status of the dispute. - */ - function disputeStatus(uint256 _disputeID) external view returns (DisputeStatus status); - - /** - * @dev Return the current ruling of a dispute. This is useful for parties to know if they should appeal. - * @param _disputeID ID of the dispute. - * @return ruling The ruling which has been given or the one which will be given if there is no appeal. - */ - function currentRuling(uint256 _disputeID) external view returns (uint256 ruling); + function cost(bytes calldata _extraData) external view returns (uint256 cost); } From 0ba4f2940025832c13f5ff6af171f76cb0e38f9a Mon Sep 17 00:00:00 2001 From: unknownunknown1 Date: Wed, 1 Dec 2021 03:10:40 +1000 Subject: [PATCH 6/6] fix(IArbitrator): change name to arbitration cost --- CHANGELOG.md | 1 + contracts/src/arbitration/CentralizedArbitrator.sol | 10 +++++----- contracts/src/arbitration/IArbitrator.sol | 6 +++--- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2948d8743..f3bb037b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ - fix(Arbitrator): memory to calldata ([4770b1f](https://github.com/kleros/kleros-v2/commit/4770b1f)) - fix(IArbitrator): appeals removed from the standard ([02c20ce](https://github.com/kleros/kleros-v2/commit/02c20ce)) +- fix(IArbitrator): interface simplification ([e81fb8b](https://github.com/kleros/kleros-v2/commit/e81fb8b)) - fix(IArbitrator): replaced appealCost with fundingStatus ([f189dd9](https://github.com/kleros/kleros-v2/commit/f189dd9)) - feat: modern toolchain setup and simple RNG smart contracts ([17f6a76](https://github.com/kleros/kleros-v2/commit/17f6a76)) - feat(Arbitration): standard update ([ed930de](https://github.com/kleros/kleros-v2/commit/ed930de)) diff --git a/contracts/src/arbitration/CentralizedArbitrator.sol b/contracts/src/arbitration/CentralizedArbitrator.sol index cfc0bf4a1..0e37e8053 100644 --- a/contracts/src/arbitration/CentralizedArbitrator.sol +++ b/contracts/src/arbitration/CentralizedArbitrator.sol @@ -50,7 +50,7 @@ contract CentralizedArbitrator is IArbitrator { address public owner = msg.sender; // Owner of the contract. uint256 public appealDuration; // The duration of the appeal period. - uint256 private arbitrationFee; // The cost to create a dispute. Made private because of the cost() getter. + uint256 private arbitrationFee; // The cost to create a dispute. Made private because of the arbitrationCost() getter. uint256 public appealFee; // The cost to fund one of the choices, not counting the additional fee stake amount. DisputeStruct[] public disputes; // Stores the dispute info. disputes[disputeID]. @@ -155,7 +155,7 @@ contract CentralizedArbitrator is IArbitrator { } /** @dev Create a dispute. Must be called by the arbitrable contract. - * Must be paid at least cost(). + * Must be paid at least arbitrationCost(). * @param _choices Amount of choices the arbitrator can make in this dispute. * @param _extraData Can be used to give additional info on the dispute to be created. * @return disputeID ID of the dispute created. @@ -166,8 +166,8 @@ contract CentralizedArbitrator is IArbitrator { override returns (uint256 disputeID) { - uint256 arbitrationCost = cost(_extraData); - require(msg.value >= arbitrationCost, "Not enough ETH to cover arbitration costs."); + uint256 localArbitrationCost = arbitrationCost(_extraData); + require(msg.value >= localArbitrationCost, "Not enough ETH to cover arbitration costs."); disputeID = disputes.length; disputes.push( DisputeStruct({ @@ -329,7 +329,7 @@ contract CentralizedArbitrator is IArbitrator { /** @dev Cost of arbitration. * @return fee The required amount. */ - function cost( + function arbitrationCost( bytes calldata /*_extraData*/ ) public view override returns (uint256 fee) { return arbitrationFee; diff --git a/contracts/src/arbitration/IArbitrator.sol b/contracts/src/arbitration/IArbitrator.sol index d63a5371f..6d4e3953a 100644 --- a/contracts/src/arbitration/IArbitrator.sol +++ b/contracts/src/arbitration/IArbitrator.sol @@ -10,7 +10,7 @@ import "./IArbitrable.sol"; * Unlike the ERC-792 this standard doesn't have anything related to appeals, so each arbitrator can implement an appeal system that suits it the most. * When developing arbitrator contracts we need to: * - Define the functions for dispute creation (createDispute). Don't forget to store the arbitrated contract and the disputeID (which should be unique, may nbDisputes). - * - Define the functions for cost display (cost). + * - Define the functions for cost display (arbitrationCost). * - Allow giving rulings. For this a function must call arbitrable.rule(disputeID, ruling). */ interface IArbitrator { @@ -23,7 +23,7 @@ interface IArbitrator { /** * @dev Create a dispute. Must be called by the arbitrable contract. - * Must pay at least cost(_extraData). + * Must pay at least arbitrationCost(_extraData). * @param _choices Amount of choices the arbitrator can make in this dispute. * @param _extraData Can be used to give additional info on the dispute to be created. * @return disputeID ID of the dispute created. @@ -35,5 +35,5 @@ interface IArbitrator { * @param _extraData Can be used to give additional info on the dispute to be created. * @return cost Required cost of arbitration. */ - function cost(bytes calldata _extraData) external view returns (uint256 cost); + function arbitrationCost(bytes calldata _extraData) external view returns (uint256 cost); }