├── .gitattributes ├── .gitignore ├── README.md ├── contracts ├── Finer.sol ├── Migrations.sol ├── ProofOfHumanity.sol ├── libraries │ └── CappedMath.sol └── test-purposes │ ├── AppealableArbitrator.sol │ ├── CentralizedArbitrator.sol │ └── EnhancedAppealableArbitrator.sol ├── migrations └── 1_initial_migration.js ├── package.json ├── test ├── .gitkeep └── proof-of-humanity.js ├── truffle-config.js └── yarn.lock /.gitattributes: -------------------------------------------------------------------------------- 1 | # Solidity syntax highlighting 2 | # See: https://github.com/github/linguist/pull/3973 3 | *.sol linguist-language=Solidity 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | node_modules/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Proof-Of-Humanity 2 | Proof-of-Humanity smart contract 3 | -------------------------------------------------------------------------------- /contracts/Finer.sol: -------------------------------------------------------------------------------- 1 | /** 2 | * @authors: [@unknownunknown1] 3 | * @reviewers: [@mtsalenc] 4 | * @auditors: [] 5 | * @bounties: [] 6 | * @deployments: [] 7 | */ 8 | 9 | pragma solidity ^0.5.13; 10 | 11 | import "@kleros/erc-792/contracts/IArbitrator.sol"; 12 | 13 | /** 14 | * @title Finer 15 | * The contract that allows to pay the fines for vouching for bad submissions in the ProofOfHumanity smart contract. 16 | * The fines should be paid to the challenger that exposed the bad submission of a particular request. 17 | * Note that the paid addresses and related amounts are not stored because the fine is not tied to the blockchain addresses but to the actual people. 18 | * The fact of funding as well as the required amount will be established and recorded off-chain. 19 | */ 20 | contract Finer { 21 | 22 | uint public constant CHALLENGER_WON = 2; 23 | IArbitrator public arbitrator; // The arbitrator contract. 24 | 25 | /** 26 | * @dev Emitted when a fine is paid. 27 | * @param _challenger The address of the challenger to pay the fines to. 28 | * @param _voucher The address of the penalized voucher that pays the fine. 29 | * @param _disputeID The ID of the related dispute that was ruled in favor of the challenger. 30 | * @param _value The amount paid. 31 | */ 32 | event FinePaid(address indexed _challenger, address indexed _voucher, uint indexed _disputeID, uint _value); 33 | 34 | constructor(IArbitrator _arbitrator) public { 35 | arbitrator = _arbitrator; 36 | } 37 | 38 | /** @dev Pays the fine and sends it to a particular address. Emits an event. 39 | * @param _challenger The address to pay the fine to. 40 | * @param _disputeID ID of the dispute in the arbitrator contract that the challenger won. 41 | */ 42 | function payFine(address payable _challenger, uint _disputeID) external payable { 43 | require(arbitrator.disputeStatus(_disputeID) == IArbitrator.DisputeStatus.Solved, "Dispute is not over yet"); 44 | require(arbitrator.currentRuling(_disputeID) == CHALLENGER_WON, "No fine for this dispute"); 45 | _challenger.transfer(msg.value); 46 | emit FinePaid(_challenger, msg.sender, _disputeID, msg.value); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.13; 2 | 3 | contract Migrations { 4 | address public owner; 5 | uint public last_completed_migration; 6 | 7 | modifier restricted() { 8 | if (msg.sender == owner) _; 9 | } 10 | 11 | constructor() public { 12 | owner = msg.sender; 13 | } 14 | 15 | function setCompleted(uint completed) public restricted { 16 | last_completed_migration = completed; 17 | } 18 | 19 | function upgrade(address new_address) public restricted { 20 | Migrations upgraded = Migrations(new_address); 21 | upgraded.setCompleted(last_completed_migration); 22 | } 23 | } -------------------------------------------------------------------------------- /contracts/ProofOfHumanity.sol: -------------------------------------------------------------------------------- 1 | /** 2 | * @authors: [@unknownunknown1] 3 | * @reviewers: [@fnanni-0*, @mtsalenc*, @nix1g*] 4 | * @auditors: [] 5 | * @bounties: [] 6 | * @deployments: [] 7 | * @tools: [MythX] 8 | */ 9 | 10 | pragma solidity ^0.5.13; 11 | 12 | /* solium-disable max-len*/ 13 | /* solium-disable error-reason */ 14 | import "@kleros/erc-792/contracts/IArbitrable.sol"; 15 | import "@kleros/erc-792/contracts/erc-1497/IEvidence.sol"; 16 | import "@kleros/erc-792/contracts/IArbitrator.sol"; 17 | 18 | import "./libraries/CappedMath.sol"; 19 | 20 | /** 21 | * @title ProofOfHumanity 22 | * This contract is a curated registry for people. The users are identified by their address and can be added or removed through the request-challenge protocol. 23 | * In order to challenge a registration request the challenger must provide one of the four reasons. 24 | * New requests firstly should gain sufficient amount of vouches from other registered users and only after that they can be accepted or challenged. 25 | * The users who vouched for submission that lost the challenge with the reason Duplicate or DoesNotExist would be penalized with optional fine or ban period. 26 | * NOTE: This contract trusts that the Arbitrator is honest and will not reenter or modify its costs during a call. 27 | * The arbitrator must support appeal period. 28 | */ 29 | contract ProofOfHumanity is IArbitrable, IEvidence { 30 | using CappedMath for uint; 31 | using CappedMath for uint64; 32 | 33 | /* Constants */ 34 | 35 | uint private constant RULING_OPTIONS = 2; // The amount of non 0 choices the arbitrator can give. 36 | uint private constant FULL_REASONS_SET = 15; // Indicates that reasons' bitmap is full. 0b1111. 37 | uint private constant MULTIPLIER_DIVISOR = 10000; // Divisor parameter for multipliers. 38 | 39 | /* Enums */ 40 | 41 | enum Status { 42 | None, // The submission doesn't have a pending status. 43 | Vouching, // The submission is in the state where it can be vouched for and crowdfunded. 44 | PendingRegistration, // The submission is in the state where it can be challenged. Or accepted to the list, if there are no challenges within the time limit. 45 | PendingRemoval // The submission is in the state where it can be challenged. Or removed from the list, if there are no challenges within the time limit. 46 | } 47 | 48 | enum Party { 49 | None, // Party per default when there is no challenger or requester. Also used for unconclusive ruling. 50 | Requester, // Party that made the request to change a status. 51 | Challenger // Party that challenged the request to change a status. 52 | } 53 | 54 | enum Reason { 55 | None, // No reason specified. This option should be used to challenge removal requests. 56 | IncorrectSubmission, // The submission does not comply with the submission rules. 57 | Deceased, // The submitter has existed but does not exist anymore. 58 | Duplicate, // The submitter is already registered. The challenger has to point to the identity already registered or to a duplicate submission. 59 | DoesNotExist // The submitter is not real. For example, this can be used for videos showing computer generated persons. 60 | } 61 | 62 | /* Structs */ 63 | 64 | struct Submission { 65 | Status status; // The current status of the submission. 66 | bool registered; // Whether the submission is in the registry or not. Note that a registered submission won't have privileges (e.g. vouching) if its duration expired. 67 | bool hasVouched; // True if this submission used its vouch for another submission. 68 | uint64 submissionTime; // The time when the submission was accepted to the list. 69 | uint64 renewalTimestamp; // The time after which it becomes possible to reapply the submission. 70 | uint64 index; // Index of a submission in the array of submissions. 71 | Request[] requests; // List of status change requests made for the submission. 72 | } 73 | 74 | struct Request { 75 | bool disputed; // True if a dispute was raised. Note that the request can enter disputed state multiple times, once per reason. 76 | bool resolved; // True if the request is executed and/or all raised disputes are resolved. 77 | bool requesterLost; // True if the requester has already had a dispute that wasn't ruled in his favor. 78 | Reason currentReason; // Current reason a registration request was challenged with. Is left empty for removal requests. 79 | uint16 nbParallelDisputes; // Tracks the number of simultaneously raised disputes. Parallel disputes are only allowed for reason Duplicate. 80 | uint32 lastProcessedVouch; // Stores the index of the last processed vouch in the array of vouches. Is used for partial processing of the vouches in resolved submissions. 81 | uint64 currentDuplicateIndex; // Stores the array index of the duplicate submission provided by the challenger who is currently winning. 82 | uint32 arbitratorDataID; // The index of the relevant arbitratorData struct. All the arbitrator info is stored in a separate struct to reduce gas cost. 83 | uint64 lastStatusChange; // Time when submission's status was last updated. Is used to track when the challenge period ends. 84 | address payable requester; // Address that made a request. It matches submissionID in case of registration requests. 85 | address payable ultimateChallenger; // Address of the challenger who won a dispute and who users that vouched for the request must pay the fines to. 86 | uint usedReasons; // Bitmap of the reasons used by challengers of this request. 87 | address[] vouches; // Stores the addresses of all submissions that vouched for this request. 88 | Challenge[] challenges; // Stores all the challenges of this request. 89 | mapping(address => bool) challengeDuplicates; // Indicates whether a certain duplicate address has been used in a challenge or not. 90 | } 91 | 92 | // Some arrays below have 3 elements to map with the Party enums for better readability: 93 | // - 0: is unused, matches `Party.None`. 94 | // - 1: for `Party.Requester`. 95 | // - 2: for `Party.Challenger`. 96 | struct Round { 97 | uint[3] paidFees; // Tracks the fees paid by each side in this round. 98 | bool[3] hasPaid; // True when the side has fully paid its fee. False otherwise. 99 | uint feeRewards; // Sum of reimbursable fees and stake rewards available to the parties that made contributions to the side that ultimately wins a dispute. 100 | mapping(address => uint[3]) contributions; // Maps contributors to their contributions for each side. 101 | } 102 | 103 | struct Challenge { 104 | uint disputeID; // The ID of the dispute related to the challenge. 105 | Party ruling; // Ruling given by the arbitrator of the dispute. 106 | uint64 duplicateSubmissionIndex; // Index of a submission in the array of submissions, which is a supposed duplicate of a challenged submission. Is only used for reason Duplicate. 107 | address payable challenger; // Address that challenged the request. 108 | Round[] rounds; // Tracks the info of each funding round of the challenge. 109 | } 110 | 111 | // The data tied to the arbitrator that will be needed to recover the submission info for arbitrator's call. 112 | struct DisputeData { 113 | uint challengeID; // The ID of the challenge of the request. 114 | address submissionID; // The submission, which ongoing request was challenged. 115 | } 116 | 117 | struct ArbitratorData { 118 | IArbitrator arbitrator; // Address of the trusted arbitrator to solve disputes. 119 | uint96 metaEvidenceUpdates; // The meta evidence to be used in disputes. 120 | bytes arbitratorExtraData; // Extra data for the arbitrator. 121 | } 122 | 123 | /* Storage */ 124 | 125 | address public governor; // The address that can make governance changes to the parameters of the contract. 126 | 127 | uint128 public submissionBaseDeposit; // The base deposit to make a new request for a submission. 128 | 129 | // Note that to ensure correct contract behaviour the sum of challengePeriodDuration and renewalPeriodDuration should be less than submissionDuration. 130 | uint64 public submissionDuration; // Time after which the registered submission will no longer be considered registered. The submitter has to reapply to the list to refresh it. 131 | uint64 public renewalPeriodDuration; // The duration of the period when the registered submission can reapply. 132 | uint64 public challengePeriodDuration; // The time after which a request becomes executable if not challenged. Note that this value should be less than the time spent on potential dispute's resolution, to avoid complications of parallel dispute handling. 133 | 134 | uint64 public sharedStakeMultiplier; // Multiplier for calculating the fee stake that must be paid in the case where arbitrator refused to arbitrate. 135 | uint64 public winnerStakeMultiplier; // Multiplier for calculating the fee stake paid by the party that won the previous round. 136 | uint64 public loserStakeMultiplier; // Multiplier for calculating the fee stake paid by the party that lost the previous round. 137 | 138 | uint public requiredNumberOfVouches; // The number of registered users that have to vouch for a new registration request in order for it to enter PendingRegistration state. 139 | 140 | address[] public submissionList; // List of IDs of all submissions. 141 | ArbitratorData[] public arbitratorDataList; // Stores the arbitrator data of the contract. Updated each time the data is changed. 142 | 143 | mapping(address => Submission) public submissions; // Maps the submission ID to its data. submissions[submissionID]. 144 | mapping(address => mapping(address => bool)) public vouches; // Indicates whether or not the voucher has vouched for a certain submission. vouches[voucherID][submissionID]. 145 | mapping(address => mapping(uint => DisputeData)) public arbitratorDisputeIDToDisputeData; // Maps a dispute ID with its data. arbitratorDisputeIDToDisputeData[arbitrator][disputeID]. 146 | 147 | /* Events */ 148 | 149 | /** 150 | * @dev Emitted when a vouch is added. 151 | * @param _submissionID The submission that receives the vouch. 152 | * @param _voucher The address that vouched. 153 | */ 154 | event VouchAdded(address indexed _submissionID, address indexed _voucher); 155 | 156 | /** 157 | * @dev Emitted when a vouch is removed. 158 | * @param _submissionID The submission which vouch is removed. 159 | * @param _voucher The address that removes its vouch. 160 | */ 161 | event VouchRemoved(address indexed _submissionID, address indexed _voucher); 162 | 163 | /** @dev Emitted when the reapplication request is made. 164 | * @param _submissionID The ID of the submission. 165 | * @param _requestID The ID of the newly created request. 166 | */ 167 | event SubmissionReapplied(address indexed _submissionID, uint _requestID); 168 | 169 | /** @dev Emitted when the submission is challenged. 170 | * @param _submissionID The ID of the submission. 171 | * @param _requestID The ID of the latest request. 172 | * @param _challengeID The ID of the challenge. 173 | */ 174 | event SubmissionChallenged(address indexed _submissionID, uint indexed _requestID, uint _challengeID); 175 | 176 | /** @dev To be emitted when someone contributes to the appeal process. 177 | * @param _submissionID The ID of the submission. 178 | * @param _challengeID The index of the challenge. 179 | * @param _party The party which received the contribution. 180 | * @param _contributor The address of the contributor. 181 | * @param _amount The amount contributed. 182 | */ 183 | event AppealContribution(address indexed _submissionID, uint indexed _challengeID, Party _party, address indexed _contributor, uint _amount); 184 | 185 | /** @dev Emitted when one of the parties successfully paid its appeal fees. 186 | * @param _submissionID The ID of the submission. 187 | * @param _challengeID The index of the challenge which appeal was funded. 188 | * @param _side The side that is fully funded. 189 | */ 190 | event HasPaidAppealFee(address indexed _submissionID, uint indexed _challengeID, Party _side); 191 | 192 | /** @dev Emitted when the challenge is resolved. 193 | * @param _submissionID The ID of the submission. 194 | * @param _requestID The ID of the latest request. 195 | * @param _challengeID The ID of the challenge that was resolved. 196 | */ 197 | event ChallengeResolved(address indexed _submissionID, uint indexed _requestID, uint _challengeID); 198 | 199 | /** @dev Constructor. 200 | * @param _arbitrator The trusted arbitrator to resolve potential disputes. 201 | * @param _arbitratorExtraData Extra data for the trusted arbitrator contract. 202 | * @param _registrationMetaEvidence The URI of the meta evidence object for registration requests. 203 | * @param _clearingMetaEvidence The URI of the meta evidence object for clearing requests. 204 | * @param _submissionBaseDeposit The base deposit to make a request for a submission. 205 | * @param _submissionDuration Time in seconds during which the registered submission won't automatically lose its status. 206 | * @param _renewalPeriodDuration Value that defines the duration of submission's renewal period. 207 | * @param _challengePeriodDuration The time in seconds during which the request can be challenged. 208 | * @param _sharedStakeMultiplier Multiplier of the arbitration cost that each party has to pay as fee stake for a round when there is no winner/loser in the previous round (e.g. when it's the first round or the arbitrator refused to arbitrate). In basis points. 209 | * @param _winnerStakeMultiplier Multiplier of the arbitration cost that the winner has to pay as fee stake for a round in basis points. 210 | * @param _loserStakeMultiplier Multiplier of the arbitration cost that the loser has to pay as fee stake for a round in basis points. 211 | * @param _requiredNumberOfVouches The number of vouches the submission has to have to pass from Vouching to PendingRegistration state. 212 | */ 213 | constructor( 214 | IArbitrator _arbitrator, 215 | bytes memory _arbitratorExtraData, 216 | string memory _registrationMetaEvidence, 217 | string memory _clearingMetaEvidence, 218 | uint128 _submissionBaseDeposit, 219 | uint64 _submissionDuration, 220 | uint64 _renewalPeriodDuration, 221 | uint64 _challengePeriodDuration, 222 | uint64 _sharedStakeMultiplier, 223 | uint64 _winnerStakeMultiplier, 224 | uint64 _loserStakeMultiplier, 225 | uint _requiredNumberOfVouches 226 | ) public { 227 | emit MetaEvidence(0, _registrationMetaEvidence); 228 | emit MetaEvidence(1, _clearingMetaEvidence); 229 | 230 | governor = msg.sender; 231 | submissionBaseDeposit = _submissionBaseDeposit; 232 | submissionDuration = _submissionDuration; 233 | renewalPeriodDuration = _renewalPeriodDuration; 234 | challengePeriodDuration = _challengePeriodDuration; 235 | sharedStakeMultiplier = _sharedStakeMultiplier; 236 | winnerStakeMultiplier = _winnerStakeMultiplier; 237 | loserStakeMultiplier = _loserStakeMultiplier; 238 | requiredNumberOfVouches = _requiredNumberOfVouches; 239 | 240 | ArbitratorData storage arbitratorData = arbitratorDataList[arbitratorDataList.length++]; 241 | arbitratorData.arbitrator = _arbitrator; 242 | arbitratorData.arbitratorExtraData = _arbitratorExtraData; 243 | } 244 | 245 | /* External and Public */ 246 | 247 | // ************************ // 248 | // * Governance * // 249 | // ************************ // 250 | 251 | /** @dev Allows the governor to directly add a new submission to the list as a part of the seeding event. 252 | * @param _submissionID The address of a newly added submission. 253 | * @param _evidence A link to evidence using its URI. 254 | */ 255 | function addSubmissionManually(address _submissionID, string calldata _evidence) external { 256 | require(isGovernor(msg.sender), "The caller must be the governor."); 257 | Submission storage submission = submissions[_submissionID]; 258 | require(submission.requests.length == 0, "Submission already been created"); 259 | submission.index = uint64(submissionList.length); 260 | submissionList.push(_submissionID); 261 | 262 | Request storage request = submission.requests[submission.requests.length++]; 263 | submission.registered = true; 264 | submission.submissionTime = uint64(now); 265 | submission.renewalTimestamp = uint64(now).addCap64(submissionDuration.subCap64(renewalPeriodDuration)); 266 | request.arbitratorDataID = uint32(arbitratorDataList.length - 1); 267 | request.resolved = true; 268 | 269 | if (bytes(_evidence).length > 0) 270 | emit Evidence(arbitratorDataList[arbitratorDataList.length - 1].arbitrator, submission.requests.length - 1 + uint(_submissionID), msg.sender, _evidence); 271 | } 272 | 273 | /** @dev Allows the governor to directly remove a registered entry from the list as a part of the seeding event. 274 | * @param _submissionID The address of a submission to remove. 275 | */ 276 | function removeSubmissionManually(address _submissionID) external { 277 | require(isGovernor(msg.sender), "The caller must be the governor."); 278 | Submission storage submission = submissions[_submissionID]; 279 | require(submission.registered && submission.status == Status.None, "Wrong status"); 280 | submission.registered = false; 281 | } 282 | 283 | /** @dev Change the base amount required as a deposit to make a request for a submission. 284 | * @param _submissionBaseDeposit The new base amount of wei required to make a new request. 285 | */ 286 | function changeSubmissionBaseDeposit(uint128 _submissionBaseDeposit) external { 287 | require(isGovernor(msg.sender), "The caller must be the governor."); 288 | submissionBaseDeposit = _submissionBaseDeposit; 289 | } 290 | 291 | /** @dev Change the time after which the registered status of a submission expires. 292 | * Note that in order to avoid unexpected behaviour the new value must fulfill: submissionDuration > challengePeriodDuration + renewalPeriodDuration. 293 | * @param _submissionDuration The new duration of the time the submission is considered registered. 294 | */ 295 | function changeSubmissionDuration(uint64 _submissionDuration) external { 296 | require(isGovernor(msg.sender), "The caller must be the governor."); 297 | submissionDuration = _submissionDuration; 298 | } 299 | 300 | /** @dev Change the time during which reapplication becomes possible. 301 | * Note that in order to avoid unexpected behaviour the new value must fulfill: submissionDuration > challengePeriodDuration + renewalPeriodDuration. 302 | * @param _renewalPeriodDuration The new value that defines the duration of submission's renewal period. 303 | */ 304 | function changeRenewalPeriodDuration(uint64 _renewalPeriodDuration) external { 305 | require(isGovernor(msg.sender), "The caller must be the governor."); 306 | renewalPeriodDuration = _renewalPeriodDuration; 307 | } 308 | 309 | /** @dev Change the duration of the challenge period. 310 | * Note that in order to avoid unexpected behaviour the new value must fulfill: submissionDuration > challengePeriodDuration + renewalPeriodDuration. 311 | * @param _challengePeriodDuration The new duration of the challenge period. 312 | */ 313 | function changeChallengePeriodDuration(uint64 _challengePeriodDuration) external { 314 | require(isGovernor(msg.sender), "The caller must be the governor."); 315 | challengePeriodDuration = _challengePeriodDuration; 316 | } 317 | 318 | /** @dev Change the number of vouches required for the request to pass to the pending state. 319 | * @param _requiredNumberOfVouches The new required number of vouches. 320 | */ 321 | function changeRequiredNumberOfVouches(uint _requiredNumberOfVouches) external { 322 | require(isGovernor(msg.sender), "The caller must be the governor."); 323 | requiredNumberOfVouches = _requiredNumberOfVouches; 324 | } 325 | 326 | /** @dev Change the proportion of arbitration fees that must be paid as fee stake by parties when there is no winner or loser. 327 | * @param _sharedStakeMultiplier Multiplier of arbitration fees that must be paid as fee stake. In basis points. 328 | */ 329 | function changeSharedStakeMultiplier(uint64 _sharedStakeMultiplier) external { 330 | require(isGovernor(msg.sender), "The caller must be the governor."); 331 | sharedStakeMultiplier = _sharedStakeMultiplier; 332 | } 333 | 334 | /** @dev Change the proportion of arbitration fees that must be paid as fee stake by the winner of the previous round. 335 | * @param _winnerStakeMultiplier Multiplier of arbitration fees that must be paid as fee stake. In basis points. 336 | */ 337 | function changeWinnerStakeMultiplier(uint64 _winnerStakeMultiplier) external { 338 | require(isGovernor(msg.sender), "The caller must be the governor."); 339 | winnerStakeMultiplier = _winnerStakeMultiplier; 340 | } 341 | 342 | /** @dev Change the proportion of arbitration fees that must be paid as fee stake by the party that lost the previous round. 343 | * @param _loserStakeMultiplier Multiplier of arbitration fees that must be paid as fee stake. In basis points. 344 | */ 345 | function changeLoserStakeMultiplier(uint64 _loserStakeMultiplier) external { 346 | require(isGovernor(msg.sender), "The caller must be the governor."); 347 | loserStakeMultiplier = _loserStakeMultiplier; 348 | } 349 | 350 | /** @dev Change the governor of the contract. 351 | * @param _governor The address of the new governor. 352 | */ 353 | function changeGovernor(address _governor) external { 354 | require(isGovernor(msg.sender), "The caller must be the governor."); 355 | governor = _governor; 356 | } 357 | 358 | /** @dev Update the meta evidence used for disputes. 359 | * @param _registrationMetaEvidence The meta evidence to be used for future registration request disputes. 360 | * @param _clearingMetaEvidence The meta evidence to be used for future clearing request disputes. 361 | */ 362 | function changeMetaEvidence(string calldata _registrationMetaEvidence, string calldata _clearingMetaEvidence) external { 363 | require(isGovernor(msg.sender), "The caller must be the governor."); 364 | ArbitratorData storage arbitratorData = arbitratorDataList[arbitratorDataList.length - 1]; 365 | uint96 newMetaEvidenceUpdates = arbitratorData.metaEvidenceUpdates + 1; 366 | arbitratorDataList.push(ArbitratorData({ 367 | arbitrator: arbitratorData.arbitrator, 368 | metaEvidenceUpdates: newMetaEvidenceUpdates, 369 | arbitratorExtraData: arbitratorData.arbitratorExtraData 370 | })); 371 | emit MetaEvidence(2 * newMetaEvidenceUpdates, _registrationMetaEvidence); 372 | emit MetaEvidence(2 * newMetaEvidenceUpdates + 1, _clearingMetaEvidence); 373 | } 374 | 375 | /** @dev Change the arbitrator to be used for disputes that may be raised in the next requests. The arbitrator is trusted to support appeal periods and not reenter. 376 | * @param _arbitrator The new trusted arbitrator to be used in the next requests. 377 | * @param _arbitratorExtraData The extra data used by the new arbitrator. 378 | */ 379 | function changeArbitrator(IArbitrator _arbitrator, bytes calldata _arbitratorExtraData) external { 380 | require(isGovernor(msg.sender), "The caller must be the governor."); 381 | ArbitratorData storage arbitratorData = arbitratorDataList[arbitratorDataList.length - 1]; 382 | arbitratorDataList.push(ArbitratorData({ 383 | arbitrator: _arbitrator, 384 | metaEvidenceUpdates: arbitratorData.metaEvidenceUpdates, 385 | arbitratorExtraData: _arbitratorExtraData 386 | })); 387 | } 388 | 389 | // ************************ // 390 | // * Requests * // 391 | // ************************ // 392 | 393 | /** @dev Make a request to add a new entry to the list. Paying the full deposit right away is not required as it can be crowdfunded later. 394 | * @param _evidence A link to evidence using its URI. 395 | */ 396 | function addSubmission(string calldata _evidence) external payable { 397 | Submission storage submission = submissions[msg.sender]; 398 | require(!submission.registered && submission.status == Status.None, "Wrong status"); 399 | if (submission.requests.length == 0) { 400 | submission.index = uint64(submissionList.length); 401 | submissionList.push(msg.sender); 402 | } 403 | submission.status = Status.Vouching; 404 | requestStatusChange(msg.sender, _evidence); 405 | } 406 | 407 | /** @dev Make a request to refresh a submissionDuration. Paying the full deposit right away is not required as it can be crowdfunded later. 408 | * Note that the user can reapply even when current submissionDuration has not expired, but only after the start of renewal period. 409 | * @param _evidence A link to evidence using its URI. 410 | */ 411 | function reapplySubmission(string calldata _evidence) external payable { 412 | Submission storage submission = submissions[msg.sender]; 413 | require(submission.registered && submission.status == Status.None, "Wrong status"); 414 | require(now >= submission.renewalTimestamp, "Can't reapply yet"); 415 | submission.status = Status.Vouching; 416 | emit SubmissionReapplied(msg.sender, submission.requests.length); 417 | requestStatusChange(msg.sender, _evidence); 418 | } 419 | 420 | /** @dev Make a request to remove a submission from the list. Requires full deposit. Accepts enough ETH to cover the deposit, reimburses the rest. 421 | * Note that this request can't be made during the renewal period to avoid spam leading to submission's expiration. 422 | * @param _submissionID The address of the submission to remove. 423 | * @param _evidence A link to evidence using its URI. 424 | */ 425 | function removeSubmission(address _submissionID, string calldata _evidence) external payable { 426 | Submission storage submission = submissions[_submissionID]; 427 | require(submission.registered && submission.status == Status.None, "Wrong status"); 428 | require(now < submission.renewalTimestamp || now - submission.submissionTime > submissionDuration, "Can't remove during renewal"); 429 | submission.status = Status.PendingRemoval; 430 | requestStatusChange(_submissionID, _evidence); 431 | } 432 | 433 | /** @dev Fund the requester's deposit. Accepts enough ETH to cover the deposit, reimburses the rest. 434 | * @param _submissionID The address of the submission which ongoing request to fund. 435 | */ 436 | function fundSubmission(address _submissionID) external payable { 437 | Submission storage submission = submissions[_submissionID]; 438 | require(submission.status == Status.Vouching, "Wrong status"); 439 | Request storage request = submission.requests[submission.requests.length - 1]; 440 | Challenge storage challenge = request.challenges[request.challenges.length - 1]; 441 | Round storage round = challenge.rounds[challenge.rounds.length - 1]; 442 | 443 | ArbitratorData storage arbitratorData = arbitratorDataList[request.arbitratorDataID]; 444 | uint arbitrationCost = arbitratorData.arbitrator.arbitrationCost(arbitratorData.arbitratorExtraData); 445 | uint totalCost = arbitrationCost.addCap(submissionBaseDeposit); 446 | contribute(round, Party.Requester, msg.sender, msg.value, totalCost); 447 | 448 | if (round.paidFees[uint(Party.Requester)] >= totalCost) 449 | round.hasPaid[uint(Party.Requester)] = true; 450 | } 451 | 452 | /** @dev Vouch for the submission. 453 | * @param _submissionID The address of the submission to vouch for. 454 | */ 455 | function addVouch(address _submissionID) external { 456 | Submission storage submission = submissions[_submissionID]; 457 | Submission storage voucher = submissions[msg.sender]; 458 | require(submission.status == Status.Vouching, "Wrong status"); 459 | require(voucher.registered && now - voucher.submissionTime <= submissionDuration, "No right to vouch"); 460 | require(!vouches[msg.sender][_submissionID], "Already vouched for this"); 461 | require(_submissionID != msg.sender, "Can't vouch for yourself"); 462 | vouches[msg.sender][_submissionID] = true; 463 | emit VouchAdded(_submissionID, msg.sender); 464 | } 465 | 466 | /** @dev Remove the submission's vouch that has been added earlier. 467 | * @param _submissionID The address of the submission to remove vouch from. 468 | */ 469 | function removeVouch(address _submissionID) external { 470 | Submission storage submission = submissions[_submissionID]; 471 | require(submission.status == Status.Vouching, "Wrong status"); 472 | require(vouches[msg.sender][_submissionID], "No vouch to remove"); 473 | vouches[msg.sender][_submissionID] = false; 474 | emit VouchRemoved(_submissionID, msg.sender); 475 | } 476 | 477 | /** @dev Allows to withdraw a mistakenly added submission while it's still in a vouching state. 478 | */ 479 | function withdrawSubmission() external { 480 | Submission storage submission = submissions[msg.sender]; 481 | require(submission.status == Status.Vouching, "Wrong status"); 482 | Request storage request = submission.requests[submission.requests.length - 1]; 483 | 484 | submission.status = Status.None; 485 | request.resolved = true; 486 | 487 | withdrawFeesAndRewards(msg.sender, msg.sender, submission.requests.length - 1, 0, 0); // Automatically withdraw for the requester. 488 | } 489 | 490 | /** @dev Change submission's state from Vouching to PendingRegistration if all conditions are met. 491 | * @param _submissionID The address of the submission which status to change. 492 | * @param _vouches Array of users which vouches to count. 493 | */ 494 | function changeStateToPending(address _submissionID, address[] calldata _vouches) external { 495 | Submission storage submission = submissions[_submissionID]; 496 | require(submission.status == Status.Vouching, "Wrong status"); 497 | Request storage request = submission.requests[submission.requests.length - 1]; 498 | Challenge storage challenge = request.challenges[request.challenges.length - 1]; 499 | Round storage round = challenge.rounds[challenge.rounds.length - 1]; 500 | require(round.hasPaid[uint(Party.Requester)], "Requester is not funded"); 501 | 502 | for (uint i = 0; i<_vouches.length && request.vouches.length= requiredNumberOfVouches, "Not enough valid vouches"); 512 | submission.status = Status.PendingRegistration; 513 | request.lastStatusChange = uint64(now); 514 | } 515 | 516 | /** @dev Challenge the submission's request. Accepts enough ETH to cover the deposit, reimburses the rest. 517 | * @param _submissionID The address of the submission which request to challenge. 518 | * @param _reason The reason to challenge the request. Left empty for removal requests. 519 | * @param _duplicateID The address of a supposed duplicate submission. Left empty if the reason is not Duplicate. 520 | * @param _evidence A link to evidence using its URI. Ignored if not provided. 521 | */ 522 | function challengeRequest(address _submissionID, Reason _reason, address _duplicateID, string calldata _evidence) external payable { 523 | Submission storage submission = submissions[_submissionID]; 524 | if (submission.status == Status.PendingRegistration) 525 | require(_reason != Reason.None, "Reason must be specified"); 526 | else if (submission.status == Status.PendingRemoval) 527 | require(_reason == Reason.None, "Reason must be left empty"); 528 | else 529 | revert("Wrong status"); 530 | 531 | Request storage request = submission.requests[submission.requests.length - 1]; 532 | require(now - request.lastStatusChange <= challengePeriodDuration, "Time to challenge has passed"); 533 | 534 | if (_reason == Reason.Duplicate) { 535 | require(submissions[_duplicateID].status > Status.None || submissions[_duplicateID].registered, "Wrong duplicate status"); 536 | require(_submissionID != _duplicateID, "Can't be a duplicate of itself"); 537 | require(request.currentReason == _reason || request.currentReason == Reason.None, "Another reason is active"); 538 | require(!request.challengeDuplicates[_duplicateID], "Duplicate address already used"); 539 | request.challengeDuplicates[_duplicateID] = true; 540 | } 541 | else { 542 | require(!request.disputed, "The request is disputed"); 543 | require(_duplicateID == address(0x0), "DuplicateID must be empty"); 544 | } 545 | 546 | if (request.currentReason != _reason) { 547 | uint reasonBit = 1 << (uint(_reason) - 1); // Get the bit that corresponds with reason's index. 548 | require((reasonBit & ~request.usedReasons) == reasonBit, "The reason has already been used"); 549 | 550 | request.usedReasons ^= reasonBit; // Mark the bit corresponding with reason's index as 'true', to indicate that the reason was used. 551 | request.currentReason = _reason; 552 | } 553 | 554 | Challenge storage challenge = request.challenges[request.challenges.length - 1]; 555 | Round storage round = challenge.rounds[challenge.rounds.length - 1]; 556 | ArbitratorData storage arbitratorData = arbitratorDataList[request.arbitratorDataID]; 557 | 558 | uint arbitrationCost = arbitratorData.arbitrator.arbitrationCost(arbitratorData.arbitratorExtraData); 559 | contribute(round, Party.Challenger, msg.sender, msg.value, arbitrationCost); 560 | require(round.paidFees[uint(Party.Challenger)] >= arbitrationCost, "You must fully fund your side"); 561 | round.hasPaid[uint(Party.Challenger)] = true; 562 | round.feeRewards = round.feeRewards.subCap(arbitrationCost); 563 | 564 | challenge.disputeID = arbitratorData.arbitrator.createDispute.value(arbitrationCost)(RULING_OPTIONS, arbitratorData.arbitratorExtraData); 565 | challenge.duplicateSubmissionIndex = submissions[_duplicateID].index; 566 | challenge.challenger = msg.sender; 567 | 568 | DisputeData storage disputeData = arbitratorDisputeIDToDisputeData[address(arbitratorData.arbitrator)][challenge.disputeID]; 569 | disputeData.challengeID = request.challenges.length - 1; 570 | disputeData.submissionID = _submissionID; 571 | 572 | request.disputed = true; 573 | request.nbParallelDisputes++; 574 | 575 | challenge.rounds.length++; 576 | emit SubmissionChallenged(_submissionID, submission.requests.length - 1, disputeData.challengeID); 577 | 578 | request.challenges[request.challenges.length++].rounds.length++; 579 | 580 | emit Dispute( 581 | arbitratorData.arbitrator, 582 | challenge.disputeID, 583 | submission.status == Status.PendingRegistration ? 2 * arbitratorData.metaEvidenceUpdates : 2 * arbitratorData.metaEvidenceUpdates + 1, 584 | submission.requests.length - 1 + uint(_submissionID) 585 | ); 586 | 587 | if (bytes(_evidence).length > 0) 588 | emit Evidence(arbitratorData.arbitrator, submission.requests.length - 1 + uint(_submissionID), msg.sender, _evidence); 589 | } 590 | 591 | /** @dev Takes up to the total amount required to fund a side of an appeal. Reimburses the rest. Creates an appeal if both sides are fully funded. 592 | * @param _submissionID The address of the submission which request to fund. 593 | * @param _challengeID The index of a dispute, created for the request. 594 | * @param _side The recipient of the contribution. 595 | */ 596 | function fundAppeal(address _submissionID, uint _challengeID, Party _side) external payable { 597 | require(_side == Party.Requester || _side == Party.Challenger); 598 | Submission storage submission = submissions[_submissionID]; 599 | require(submission.status == Status.PendingRegistration || submission.status == Status.PendingRemoval, "Wrong status"); 600 | Request storage request = submission.requests[submission.requests.length - 1]; 601 | require(request.disputed); 602 | 603 | Challenge storage challenge = request.challenges[_challengeID]; 604 | ArbitratorData storage arbitratorData = arbitratorDataList[request.arbitratorDataID]; 605 | 606 | (uint appealPeriodStart, uint appealPeriodEnd) = arbitratorData.arbitrator.appealPeriod(challenge.disputeID); 607 | require(now >= appealPeriodStart && now < appealPeriodEnd, "Appeal period is over"); 608 | 609 | uint multiplier; 610 | /* solium-disable indentation */ 611 | { 612 | Party winner = Party(arbitratorData.arbitrator.currentRuling(challenge.disputeID)); 613 | if (winner == _side){ 614 | multiplier = winnerStakeMultiplier; 615 | } else if (winner == Party.None){ 616 | multiplier = sharedStakeMultiplier; 617 | } else { 618 | multiplier = loserStakeMultiplier; 619 | require(now-appealPeriodStart < (appealPeriodEnd-appealPeriodStart)/2, "Appeal period is over for loser"); 620 | } 621 | } 622 | /* solium-enable indentation */ 623 | 624 | Round storage round = challenge.rounds[challenge.rounds.length - 1]; 625 | 626 | uint appealCost = arbitratorData.arbitrator.appealCost(challenge.disputeID, arbitratorData.arbitratorExtraData); 627 | uint totalCost = appealCost.addCap((appealCost.mulCap(multiplier)) / MULTIPLIER_DIVISOR); 628 | uint contribution = contribute(round, _side, msg.sender, msg.value, totalCost); 629 | emit AppealContribution(_submissionID, _challengeID, _side, msg.sender, contribution); 630 | 631 | if (round.paidFees[uint(_side)] >= totalCost) { 632 | round.hasPaid[uint(_side)] = true; 633 | emit HasPaidAppealFee(_submissionID, _challengeID, _side); 634 | } 635 | 636 | if (round.hasPaid[uint(Party.Challenger)] && round.hasPaid[uint(Party.Requester)]) { 637 | arbitratorData.arbitrator.appeal.value(appealCost)(challenge.disputeID, arbitratorData.arbitratorExtraData); 638 | challenge.rounds.length++; 639 | round.feeRewards = round.feeRewards.subCap(appealCost); 640 | } 641 | } 642 | 643 | /** @dev Execute a request if the challenge period passed and no one challenged the request. 644 | * @param _submissionID The address of the submission with the request to execute. 645 | */ 646 | function executeRequest(address _submissionID) external { 647 | Submission storage submission = submissions[_submissionID]; 648 | Request storage request = submission.requests[submission.requests.length - 1]; 649 | require(now - request.lastStatusChange > challengePeriodDuration, "Can't execute yet"); 650 | require(!request.disputed, "The request is disputed"); 651 | if (submission.status == Status.PendingRegistration) { 652 | // It is possible for the requester to lose without a dispute if he was penalized for bad vouching while reapplying. 653 | if (!request.requesterLost) { 654 | submission.registered = true; 655 | submission.submissionTime = uint64(now); 656 | submission.renewalTimestamp = uint64(now).addCap64(submissionDuration.subCap64(renewalPeriodDuration)); 657 | } 658 | } else if (submission.status == Status.PendingRemoval) 659 | submission.registered = false; 660 | else 661 | revert("Incorrect status."); 662 | 663 | submission.status = Status.None; 664 | request.resolved = true; 665 | 666 | withdrawFeesAndRewards(request.requester, _submissionID, submission.requests.length - 1, 0, 0); // Automatically withdraw for the requester. 667 | } 668 | 669 | /** @dev Deletes vouches of the resolved request, so vouchings of users who vouched for it can be used in other submissions. 670 | * Penalizes users who vouched for bad submissions. 671 | * @param _submissionID The address of the submission which vouches to iterate. 672 | * @param _requestID The ID of the request which vouches to iterate. 673 | * @param _iterations The number of iterations to go through. 674 | */ 675 | function processVouches(address _submissionID, uint _requestID, uint _iterations) external { 676 | Submission storage submission = submissions[_submissionID]; 677 | Request storage request = submission.requests[_requestID]; 678 | require(request.resolved, "Submission must be resolved"); 679 | 680 | uint endIndex = _iterations.addCap(request.lastProcessedVouch) > request.vouches.length ? 681 | request.vouches.length : _iterations.addCap(request.lastProcessedVouch); 682 | 683 | // If the ultimate challenger is defined that means that the request was ruled in favor of the challenger. 684 | bool applyPenalty = request.ultimateChallenger != address(0x0) && (request.currentReason == Reason.Duplicate || request.currentReason == Reason.DoesNotExist); 685 | for (uint i = request.lastProcessedVouch; i < endIndex; i++) { 686 | Submission storage voucher = submissions[request.vouches[i]]; 687 | voucher.hasVouched = false; 688 | if (applyPenalty) { 689 | // Check the situation when vouching address is in the middle of reapplication process. 690 | if (voucher.status == Status.Vouching || voucher.status == Status.PendingRegistration) 691 | voucher.requests[voucher.requests.length - 1].requesterLost = true; 692 | 693 | voucher.registered = false; 694 | } 695 | } 696 | request.lastProcessedVouch = uint32(endIndex); 697 | } 698 | 699 | /** @dev Reimburses contributions if no disputes were raised. If a dispute was raised, sends the fee stake rewards and reimbursements proportionally to the contributions made to the winner of a dispute. 700 | * @param _beneficiary The address that made contributions to a request. 701 | * @param _submissionID The address of the submission with the request from which to withdraw. 702 | * @param _requestID The request from which to withdraw. 703 | * @param _challengeID The ID of the challenge from which to withdraw. 704 | * @param _round The round from which to withdraw. 705 | */ 706 | function withdrawFeesAndRewards(address payable _beneficiary, address _submissionID, uint _requestID, uint _challengeID, uint _round) public { 707 | Submission storage submission = submissions[_submissionID]; 708 | Request storage request = submission.requests[_requestID]; 709 | Challenge storage challenge = request.challenges[_challengeID]; 710 | Round storage round = challenge.rounds[_round]; 711 | require(request.resolved, "Submission must be resolved"); 712 | require(_beneficiary != address(0x0), "Beneficiary must not be empty"); 713 | 714 | uint reward; 715 | if (_round != 0 && (!round.hasPaid[uint(Party.Requester)] || !round.hasPaid[uint(Party.Challenger)])) { 716 | reward = round.contributions[_beneficiary][uint(Party.Requester)] + round.contributions[_beneficiary][uint(Party.Challenger)]; 717 | round.contributions[_beneficiary][uint(Party.Requester)] = 0; 718 | round.contributions[_beneficiary][uint(Party.Challenger)] = 0; 719 | } else if (challenge.ruling == Party.None) { 720 | uint totalFeesInRound = round.paidFees[uint(Party.Challenger)] + round.paidFees[uint(Party.Requester)]; 721 | uint claimableFees = round.contributions[_beneficiary][uint(Party.Challenger)] + round.contributions[_beneficiary][uint(Party.Requester)]; 722 | reward = totalFeesInRound > 0 ? claimableFees * round.feeRewards / totalFeesInRound : 0; 723 | 724 | round.contributions[_beneficiary][uint(Party.Requester)] = 0; 725 | round.contributions[_beneficiary][uint(Party.Challenger)] = 0; 726 | } else { 727 | // Challenger, who ultimately wins, will be able to get the deposit of the requester, even if he didn't participate in the initial dispute. 728 | if (_round == 0 && _beneficiary == request.ultimateChallenger && _challengeID == 0) { 729 | reward = round.feeRewards; 730 | round.feeRewards = 0; 731 | // This condition will prevent claiming a reward, intended for the ultimate challenger. 732 | } else if (request.ultimateChallenger==address(0x0) || _challengeID!=0 || _round!=0) { 733 | reward = round.paidFees[uint(challenge.ruling)] > 0 734 | ? (round.contributions[_beneficiary][uint(challenge.ruling)] * round.feeRewards) / round.paidFees[uint(challenge.ruling)] 735 | : 0; 736 | round.contributions[_beneficiary][uint(challenge.ruling)] = 0; 737 | } 738 | } 739 | _beneficiary.send(reward); 740 | } 741 | 742 | /** @dev Give a ruling for a dispute. Can only be called by the arbitrator. TRUSTED. 743 | * Accounts for the situation where the winner loses a case due to paying less appeal fees than expected. 744 | * @param _disputeID ID of the dispute in the arbitrator contract. 745 | * @param _ruling Ruling given by the arbitrator. Note that 0 is reserved for "Refused to arbitrate". 746 | */ 747 | function rule(uint _disputeID, uint _ruling) public { 748 | Party resultRuling = Party(_ruling); 749 | DisputeData storage disputeData = arbitratorDisputeIDToDisputeData[msg.sender][_disputeID]; 750 | Submission storage submission = submissions[disputeData.submissionID]; 751 | 752 | Request storage request = submission.requests[submission.requests.length - 1]; 753 | Challenge storage challenge = request.challenges[disputeData.challengeID]; 754 | Round storage round = challenge.rounds[challenge.rounds.length - 1]; 755 | ArbitratorData storage arbitratorData = arbitratorDataList[request.arbitratorDataID]; 756 | 757 | require(_ruling <= RULING_OPTIONS); 758 | require(address(arbitratorData.arbitrator) == msg.sender); 759 | require(!request.resolved); 760 | 761 | // The ruling is inverted if the loser paid its fees. 762 | if (round.hasPaid[uint(Party.Requester)]) // If one side paid its fees, the ruling is in its favor. Note that if the other side had also paid, an appeal would have been created. 763 | resultRuling = Party.Requester; 764 | else if (round.hasPaid[uint(Party.Challenger)]) 765 | resultRuling = Party.Challenger; 766 | 767 | emit Ruling(IArbitrator(msg.sender), _disputeID, uint(resultRuling)); 768 | executeRuling(disputeData.submissionID, disputeData.challengeID, resultRuling); 769 | } 770 | 771 | /** @dev Submit a reference to evidence. EVENT. 772 | * @param _submissionID The address of the submission which the evidence is related to. 773 | * @param _evidence A link to an evidence using its URI. 774 | */ 775 | function submitEvidence(address _submissionID, string calldata _evidence) external { 776 | Submission storage submission = submissions[_submissionID]; 777 | Request storage request = submission.requests[submission.requests.length - 1]; 778 | ArbitratorData storage arbitratorData = arbitratorDataList[request.arbitratorDataID]; 779 | 780 | if (bytes(_evidence).length > 0) 781 | emit Evidence(arbitratorData.arbitrator, submission.requests.length - 1 + uint(_submissionID), msg.sender, _evidence); 782 | } 783 | 784 | /* Internal */ 785 | 786 | /** @dev Make a request to change submission's status. Paying the full deposit right away is not required for registration requests. 787 | * Note that removal requests require the full deposit and can't be crowdfunded. 788 | * @param _submissionID The address of the submission which status to change. 789 | * @param _evidence A link to evidence using its URI. 790 | */ 791 | function requestStatusChange(address _submissionID, string memory _evidence) internal { 792 | Submission storage submission = submissions[_submissionID]; 793 | Request storage request = submission.requests[submission.requests.length++]; 794 | 795 | request.requester = msg.sender; 796 | request.lastStatusChange = uint64(now); 797 | request.arbitratorDataID = uint32(arbitratorDataList.length - 1); 798 | 799 | Challenge storage challenge = request.challenges[request.challenges.length++]; 800 | Round storage round = challenge.rounds[challenge.rounds.length++]; 801 | 802 | IArbitrator _arbitrator = arbitratorDataList[arbitratorDataList.length - 1].arbitrator; 803 | uint arbitrationCost = _arbitrator.arbitrationCost(arbitratorDataList[arbitratorDataList.length - 1].arbitratorExtraData); 804 | uint totalCost = arbitrationCost.addCap(submissionBaseDeposit); 805 | contribute(round, Party.Requester, msg.sender, msg.value, totalCost); 806 | 807 | if (submission.status == Status.PendingRemoval) 808 | require(round.paidFees[uint(Party.Requester)] >= totalCost, "You must fully fund your side"); 809 | 810 | if (round.paidFees[uint(Party.Requester)] >= totalCost) 811 | round.hasPaid[uint(Party.Requester)] = true; 812 | 813 | if (bytes(_evidence).length > 0) 814 | emit Evidence(_arbitrator, submission.requests.length - 1 + uint(_submissionID), msg.sender, _evidence); 815 | } 816 | 817 | /** @dev Returns the contribution value and remainder from available ETH and required amount. 818 | * @param _available The amount of ETH available for the contribution. 819 | * @param _requiredAmount The amount of ETH required for the contribution. 820 | * @return taken The amount of ETH taken. 821 | * @return remainder The amount of ETH left from the contribution. 822 | */ 823 | function calculateContribution(uint _available, uint _requiredAmount) 824 | internal 825 | pure 826 | returns(uint taken, uint remainder) 827 | { 828 | if (_requiredAmount > _available) 829 | return (_available, 0); 830 | 831 | remainder = _available - _requiredAmount; 832 | return (_requiredAmount, remainder); 833 | } 834 | 835 | /** @dev Make a fee contribution. 836 | * @param _round The round to contribute to. 837 | * @param _side The side to contribute to. 838 | * @param _contributor The contributor. 839 | * @param _amount The amount contributed. 840 | * @param _totalRequired The total amount required for this side. 841 | * @return The amount of fees contributed. 842 | */ 843 | function contribute(Round storage _round, Party _side, address payable _contributor, uint _amount, uint _totalRequired) internal returns (uint) { 844 | uint contribution; 845 | uint remainingETH; 846 | (contribution, remainingETH) = calculateContribution(_amount, _totalRequired.subCap(_round.paidFees[uint(_side)])); 847 | _round.contributions[_contributor][uint(_side)] += contribution; 848 | _round.paidFees[uint(_side)] += contribution; 849 | _round.feeRewards += contribution; 850 | 851 | _contributor.send(remainingETH); 852 | 853 | return contribution; 854 | } 855 | 856 | /** @dev Execute the ruling of a dispute. 857 | * @param _submissionID ID of the submission. 858 | * @param _challengeID ID of the challenge, related to the dispute. 859 | * @param _winner Ruling given by the arbitrator. Note that 0 is reserved for "Refused to arbitrate". 860 | */ 861 | function executeRuling(address _submissionID, uint _challengeID, Party _winner) internal { 862 | Submission storage submission = submissions[_submissionID]; 863 | Request storage request = submission.requests[submission.requests.length - 1]; 864 | Challenge storage challenge = request.challenges[_challengeID]; 865 | 866 | if (submission.status == Status.PendingRemoval) { 867 | submission.registered = _winner != Party.Requester; 868 | submission.status = Status.None; 869 | request.resolved = true; 870 | } else if (submission.status == Status.PendingRegistration) { 871 | // For a registration request there can be more than one dispute. 872 | if (_winner == Party.Requester) { 873 | if (request.nbParallelDisputes == 1) { 874 | // Check whether or not the requester won all of his previous disputes for current reason. 875 | if (!request.requesterLost) { 876 | if (request.usedReasons == FULL_REASONS_SET) { 877 | // All reasons being used means the request can't be challenged again, so we can update its status. 878 | submission.status = Status.None; 879 | submission.registered = true; 880 | submission.submissionTime = uint64(now); 881 | submission.renewalTimestamp = uint64(now).addCap64(submissionDuration.subCap64(renewalPeriodDuration)); 882 | } else { 883 | // Refresh the state of the request so it can be challenged again. 884 | request.disputed = false; 885 | request.lastStatusChange = uint64(now); 886 | request.currentReason = Reason.None; 887 | } 888 | } else 889 | submission.status = Status.None; 890 | } 891 | // Challenger won or it’s a tie. 892 | } else { 893 | request.requesterLost = true; 894 | // Update the status of the submission if there is no more disputes left. 895 | if (request.nbParallelDisputes == 1) 896 | submission.status = Status.None; 897 | // Store the challenger that made the requester lose. Update the challenger if there is a duplicate with lower submission time, which is indicated by submission's array index. 898 | if (_winner==Party.Challenger && (request.ultimateChallenger==address(0x0) || challenge.duplicateSubmissionIndex= _a ? c : UINT_MAX; 25 | } 26 | 27 | /** 28 | * @dev Subtracts two integers, returns 0 on underflow. 29 | */ 30 | function subCap(uint _a, uint _b) internal pure returns (uint) { 31 | if (_b > _a) 32 | return 0; 33 | else 34 | return _a - _b; 35 | } 36 | 37 | /** 38 | * @dev Multiplies two unsigned integers, returns 2^256 - 1 on overflow. 39 | */ 40 | function mulCap(uint _a, uint _b) internal pure returns (uint) { 41 | // Gas optimization: this is cheaper than requiring '_a' not being zero, but the 42 | // benefit is lost if '_b' is also tested. 43 | // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522 44 | if (_a == 0) 45 | return 0; 46 | 47 | uint c = _a * _b; 48 | return c / _a == _b ? c : UINT_MAX; 49 | } 50 | 51 | function addCap64(uint64 _a, uint64 _b) internal pure returns (uint64) { 52 | uint64 c = _a + _b; 53 | return c >= _a ? c : UINT64_MAX; 54 | } 55 | 56 | 57 | function subCap64(uint64 _a, uint64 _b) internal pure returns (uint64) { 58 | if (_b > _a) 59 | return 0; 60 | else 61 | return _a - _b; 62 | } 63 | 64 | function mulCap64(uint64 _a, uint64 _b) internal pure returns (uint64) { 65 | if (_a == 0) 66 | return 0; 67 | 68 | uint64 c = _a * _b; 69 | return c / _a == _b ? c : UINT64_MAX; 70 | } 71 | } -------------------------------------------------------------------------------- /contracts/test-purposes/AppealableArbitrator.sol: -------------------------------------------------------------------------------- 1 | /** 2 | * https://contributing.kleros.io/smart-contract-workflow 3 | * @authors: [@epiqueras, @ferittuncer] 4 | * @reviewers: [] 5 | * @auditors: [] 6 | * @bounties: [] 7 | * @deployments: [] 8 | */ 9 | 10 | pragma solidity ^0.5.13; 11 | 12 | import "./CentralizedArbitrator.sol"; 13 | 14 | /** 15 | * @title AppealableArbitrator 16 | * @dev A centralized arbitrator that can be appealed. 17 | */ 18 | contract AppealableArbitrator is CentralizedArbitrator, IArbitrable { 19 | /* Structs */ 20 | 21 | struct AppealDispute { 22 | uint rulingTime; 23 | IArbitrator arbitrator; 24 | uint appealDisputeID; 25 | } 26 | 27 | /* Modifiers */ 28 | 29 | modifier onlyArbitrator {require(msg.sender == address(arbitrator), "Can only be called by the arbitrator."); _;} 30 | modifier requireAppealFee(uint _disputeID, bytes memory _extraData) { 31 | require(msg.value >= appealCost(_disputeID, _extraData), "Not enough ETH to cover appeal costs."); 32 | _; 33 | } 34 | 35 | /* Storage */ 36 | 37 | uint public timeOut; 38 | mapping(uint => AppealDispute) public appealDisputes; 39 | mapping(uint => uint) public appealDisputeIDsToDisputeIDs; 40 | IArbitrator public arbitrator; 41 | bytes public arbitratorExtraData; // Extra data to require particular dispute and appeal behaviour. 42 | 43 | /* Constructor */ 44 | 45 | /** @dev Constructs the `AppealableArbitrator` contract. 46 | * @param _arbitrationPrice The amount to be paid for arbitration. 47 | * @param _arbitrator The back up arbitrator. 48 | * @param _arbitratorExtraData Not used by this contract. 49 | * @param _timeOut The time out for the appeal period. 50 | */ 51 | constructor( 52 | uint _arbitrationPrice, 53 | IArbitrator _arbitrator, 54 | bytes memory _arbitratorExtraData, 55 | uint _timeOut 56 | ) public CentralizedArbitrator(_arbitrationPrice) { 57 | timeOut = _timeOut; 58 | } 59 | 60 | /* External */ 61 | 62 | /** @dev Changes the back up arbitrator. 63 | * @param _arbitrator The new back up arbitrator. 64 | */ 65 | function changeArbitrator(IArbitrator _arbitrator) external onlyOwner { 66 | arbitrator = _arbitrator; 67 | } 68 | 69 | /** @dev Changes the time out. 70 | * @param _timeOut The new time out. 71 | */ 72 | function changeTimeOut(uint _timeOut) external onlyOwner { 73 | timeOut = _timeOut; 74 | } 75 | 76 | /* External Views */ 77 | 78 | /** @dev Gets the specified dispute's latest appeal ID. 79 | * @param _disputeID The ID of the dispute. 80 | */ 81 | function getAppealDisputeID(uint _disputeID) external view returns(uint disputeID) { 82 | if (appealDisputes[_disputeID].arbitrator != IArbitrator(address(0))) 83 | disputeID = AppealableArbitrator(address(appealDisputes[_disputeID].arbitrator)).getAppealDisputeID(appealDisputes[_disputeID].appealDisputeID); 84 | else disputeID = _disputeID; 85 | } 86 | 87 | /* Public */ 88 | 89 | /** @dev Appeals a ruling. 90 | * @param _disputeID The ID of the dispute. 91 | * @param _extraData Additional info about the appeal. 92 | */ 93 | function appeal(uint _disputeID, bytes memory _extraData) public payable requireAppealFee(_disputeID, _extraData) { 94 | if (appealDisputes[_disputeID].arbitrator != IArbitrator(address(0))) 95 | appealDisputes[_disputeID].arbitrator.appeal.value(msg.value)(appealDisputes[_disputeID].appealDisputeID, _extraData); 96 | else { 97 | appealDisputes[_disputeID].arbitrator = arbitrator; 98 | appealDisputes[_disputeID].appealDisputeID = arbitrator.createDispute.value(msg.value)(disputes[_disputeID].choices, _extraData); 99 | appealDisputeIDsToDisputeIDs[appealDisputes[_disputeID].appealDisputeID] = _disputeID; 100 | } 101 | } 102 | 103 | /** @dev Gives a ruling. 104 | * @param _disputeID The ID of the dispute. 105 | * @param _ruling The ruling. 106 | */ 107 | function giveRuling(uint _disputeID, uint _ruling) public { 108 | require(disputes[_disputeID].status != DisputeStatus.Solved, "The specified dispute is already resolved."); 109 | if (appealDisputes[_disputeID].arbitrator != IArbitrator(address(0))) { 110 | require(IArbitrator(msg.sender) == appealDisputes[_disputeID].arbitrator, "Appealed disputes must be ruled by their back up arbitrator."); 111 | super._giveRuling(_disputeID, _ruling); 112 | } else { 113 | require(msg.sender == owner, "Not appealed disputes must be ruled by the owner."); 114 | if (disputes[_disputeID].status == DisputeStatus.Appealable) { 115 | if (now - appealDisputes[_disputeID].rulingTime > timeOut) 116 | super._giveRuling(_disputeID, disputes[_disputeID].ruling); 117 | else revert("Time out time has not passed yet."); 118 | } else { 119 | disputes[_disputeID].ruling = _ruling; 120 | disputes[_disputeID].status = DisputeStatus.Appealable; 121 | appealDisputes[_disputeID].rulingTime = now; 122 | emit AppealPossible(_disputeID, disputes[_disputeID].arbitrated); 123 | } 124 | } 125 | } 126 | 127 | /** @dev Give a ruling for a dispute. Must be called by the arbitrator. 128 | * The purpose of this function is to ensure that the address calling it has the right to rule on the contract. 129 | * @param _disputeID ID of the dispute in the IArbitrator contract. 130 | * @param _ruling Ruling given by the arbitrator. Note that 0 is reserved for "Not able/wanting to make a decision". 131 | */ 132 | function rule(uint _disputeID, uint _ruling) public onlyArbitrator { 133 | emit Ruling(IArbitrator(msg.sender),_disputeID,_ruling); 134 | 135 | executeRuling(_disputeID,_ruling); 136 | } 137 | 138 | /* Public Views */ 139 | 140 | /** @dev Gets the cost of appeal for the specified dispute. 141 | * @param _disputeID The ID of the dispute. 142 | * @param _extraData Additional info about the appeal. 143 | * @return The cost of the appeal. 144 | */ 145 | function appealCost(uint _disputeID, bytes memory _extraData) public view returns(uint cost) { 146 | if (appealDisputes[_disputeID].arbitrator != IArbitrator(address(0))) 147 | cost = appealDisputes[_disputeID].arbitrator.appealCost(appealDisputes[_disputeID].appealDisputeID, _extraData); 148 | else if (disputes[_disputeID].status == DisputeStatus.Appealable) cost = arbitrator.arbitrationCost(_extraData); 149 | else cost = NOT_PAYABLE_VALUE; 150 | } 151 | 152 | /** @dev Gets the status of the specified dispute. 153 | * @param _disputeID The ID of the dispute. 154 | * @return The status. 155 | */ 156 | function disputeStatus(uint _disputeID) public view returns(DisputeStatus status) { 157 | if (appealDisputes[_disputeID].arbitrator != IArbitrator(address(0))) 158 | status = appealDisputes[_disputeID].arbitrator.disputeStatus(appealDisputes[_disputeID].appealDisputeID); 159 | else status = disputes[_disputeID].status; 160 | } 161 | 162 | /** @dev Return the ruling of a dispute. 163 | * @param _disputeID ID of the dispute to rule. 164 | * @return ruling The ruling which would or has been given. 165 | */ 166 | function currentRuling(uint _disputeID) public view returns(uint ruling) { 167 | if (appealDisputes[_disputeID].arbitrator != IArbitrator(address(0))) // Appealed. 168 | ruling = appealDisputes[_disputeID].arbitrator.currentRuling(appealDisputes[_disputeID].appealDisputeID); // Retrieve ruling from the arbitrator whom the dispute is appealed to. 169 | else ruling = disputes[_disputeID].ruling; // Not appealed, basic case. 170 | } 171 | 172 | /* Internal */ 173 | 174 | /** @dev Executes the ruling of the specified dispute. 175 | * @param _disputeID The ID of the dispute. 176 | * @param _ruling The ruling. 177 | */ 178 | function executeRuling(uint _disputeID, uint _ruling) internal { 179 | require( 180 | appealDisputes[appealDisputeIDsToDisputeIDs[_disputeID]].arbitrator != IArbitrator(address(0)), 181 | "The dispute must have been appealed." 182 | ); 183 | giveRuling(appealDisputeIDsToDisputeIDs[_disputeID], _ruling); 184 | } 185 | } -------------------------------------------------------------------------------- /contracts/test-purposes/CentralizedArbitrator.sol: -------------------------------------------------------------------------------- 1 | /** 2 | * @authors: [@clesaege, @n1c01a5, @epiqueras, @ferittuncer] 3 | * @reviewers: [@clesaege*, @unknownunknown1*] 4 | * @auditors: [] 5 | * @bounties: [] 6 | * @deployments: [] 7 | */ 8 | 9 | pragma solidity ^0.5.13; 10 | 11 | import { IArbitrator, IArbitrable } from "@kleros/erc-792/contracts/IArbitrator.sol"; 12 | 13 | /** @title Centralized Arbitrator 14 | * @dev This is a centralized arbitrator deciding alone on the result of disputes. No appeals are possible. 15 | */ 16 | contract CentralizedArbitrator is IArbitrator { 17 | 18 | address public owner = msg.sender; 19 | uint arbitrationPrice; // Not public because arbitrationCost already acts as an accessor. 20 | uint constant NOT_PAYABLE_VALUE = (2**256-2)/2; // High value to be sure that the appeal is too expensive. 21 | 22 | struct DisputeStruct { 23 | IArbitrable arbitrated; 24 | uint choices; 25 | uint fee; 26 | uint ruling; 27 | DisputeStatus status; 28 | } 29 | 30 | modifier onlyOwner {require(msg.sender==owner, "Can only be called by the owner."); _;} 31 | modifier requireArbitrationFee(bytes memory _extraData) { 32 | require(msg.value >= arbitrationCost(_extraData), "Not enough ETH to cover arbitration costs."); 33 | _; 34 | } 35 | 36 | DisputeStruct[] public disputes; 37 | 38 | /** @dev Constructor. Set the initial arbitration price. 39 | * @param _arbitrationPrice Amount to be paid for arbitration. 40 | */ 41 | constructor(uint _arbitrationPrice) public { 42 | arbitrationPrice = _arbitrationPrice; 43 | } 44 | 45 | /** @dev Set the arbitration price. Only callable by the owner. 46 | * @param _arbitrationPrice Amount to be paid for arbitration. 47 | */ 48 | function setArbitrationPrice(uint _arbitrationPrice) public onlyOwner { 49 | arbitrationPrice = _arbitrationPrice; 50 | } 51 | 52 | /** @dev Cost of arbitration. Accessor to arbitrationPrice. 53 | * @param _extraData Not used by this contract. 54 | * @return fee Amount to be paid. 55 | */ 56 | function arbitrationCost(bytes memory _extraData) public view returns(uint fee) { 57 | return arbitrationPrice; 58 | } 59 | 60 | /** @dev Cost of appeal. Since it is not possible, it's a high value which can never be paid. 61 | * @param _disputeID ID of the dispute to be appealed. Not used by this contract. 62 | * @param _extraData Not used by this contract. 63 | * @return fee Amount to be paid. 64 | */ 65 | function appealCost(uint _disputeID, bytes memory _extraData) public view returns(uint fee) { 66 | return NOT_PAYABLE_VALUE; 67 | } 68 | 69 | /** @dev Create a dispute. Must be called by the arbitrable contract. 70 | * Must be paid at least arbitrationCost(). 71 | * @param _choices Amount of choices the arbitrator can make in this dispute. When ruling ruling<=choices. 72 | * @param _extraData Can be used to give additional info on the dispute to be created. 73 | * @return disputeID ID of the dispute created. 74 | */ 75 | function createDispute(uint _choices, bytes memory _extraData) public payable requireArbitrationFee(_extraData) returns(uint disputeID) { 76 | disputeID = disputes.push(DisputeStruct({ 77 | arbitrated: IArbitrable(msg.sender), 78 | choices: _choices, 79 | fee: msg.value, 80 | ruling: 0, 81 | status: DisputeStatus.Waiting 82 | })) - 1; // Create the dispute and return its number. 83 | emit DisputeCreation(disputeID, IArbitrable(msg.sender)); 84 | } 85 | 86 | /** @dev Give a ruling. UNTRUSTED. 87 | * @param _disputeID ID of the dispute to rule. 88 | * @param _ruling Ruling given by the arbitrator. Note that 0 means "Not able/wanting to make a decision". 89 | */ 90 | function _giveRuling(uint _disputeID, uint _ruling) internal { 91 | DisputeStruct storage dispute = disputes[_disputeID]; 92 | require(_ruling <= dispute.choices, "Invalid ruling."); 93 | require(dispute.status != DisputeStatus.Solved, "The dispute must not be solved already."); 94 | 95 | dispute.ruling = _ruling; 96 | dispute.status = DisputeStatus.Solved; 97 | 98 | msg.sender.send(dispute.fee); // Avoid blocking. 99 | dispute.arbitrated.rule(_disputeID,_ruling); 100 | } 101 | 102 | /** @dev Give a ruling. UNTRUSTED. 103 | * @param _disputeID ID of the dispute to rule. 104 | * @param _ruling Ruling given by the arbitrator. Note that 0 means "Not able/wanting to make a decision". 105 | */ 106 | function giveRuling(uint _disputeID, uint _ruling) public onlyOwner { 107 | return _giveRuling(_disputeID, _ruling); 108 | } 109 | 110 | /** @dev Return the status of a dispute. 111 | * @param _disputeID ID of the dispute to rule. 112 | * @return status The status of the dispute. 113 | */ 114 | function disputeStatus(uint _disputeID) public view returns(DisputeStatus status) { 115 | return disputes[_disputeID].status; 116 | } 117 | 118 | /** @dev Return the ruling of a dispute. 119 | * @param _disputeID ID of the dispute to rule. 120 | * @return ruling The ruling which would or has been given. 121 | */ 122 | function currentRuling(uint _disputeID) public view returns(uint ruling) { 123 | return disputes[_disputeID].ruling; 124 | } 125 | } -------------------------------------------------------------------------------- /contracts/test-purposes/EnhancedAppealableArbitrator.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.13; 2 | 3 | import "./AppealableArbitrator.sol"; 4 | 5 | /** 6 | * @title EnhancedAppealableArbitrator 7 | * @author Enrique Piqueras - 8 | * @dev Implementation of `AppealableArbitrator` that supports `appealPeriod`. 9 | */ 10 | contract EnhancedAppealableArbitrator is AppealableArbitrator { 11 | /* Constructor */ 12 | 13 | /* solium-disable no-empty-blocks */ 14 | /** @dev Constructs the `EnhancedAppealableArbitrator` contract. 15 | * @param _arbitrationPrice The amount to be paid for arbitration. 16 | * @param _arbitrator The back up arbitrator. 17 | * @param _arbitratorExtraData Not used by this contract. 18 | * @param _timeOut The time out for the appeal period. 19 | */ 20 | constructor( 21 | uint _arbitrationPrice, 22 | IArbitrator _arbitrator, 23 | bytes memory _arbitratorExtraData, 24 | uint _timeOut 25 | ) public AppealableArbitrator(_arbitrationPrice, _arbitrator, _arbitratorExtraData, _timeOut) {} 26 | /* solium-enable no-empty-blocks */ 27 | 28 | /* Public Views */ 29 | 30 | /** @dev Compute the start and end of the dispute's current or next appeal period, if possible. 31 | * @param _disputeID ID of the dispute. 32 | * @return The start and end of the period. 33 | */ 34 | function appealPeriod(uint _disputeID) public view returns(uint start, uint end) { 35 | if (appealDisputes[_disputeID].arbitrator != IArbitrator(address(0))) 36 | (start, end) = appealDisputes[_disputeID].arbitrator.appealPeriod(appealDisputes[_disputeID].appealDisputeID); 37 | else { 38 | start = appealDisputes[_disputeID].rulingTime; 39 | require(start != 0, "The specified dispute is not appealable."); 40 | end = start + timeOut; 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | const Migrations = artifacts.require('Migrations') 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(Migrations) 5 | } 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "proof-of-humanity", 3 | "version": "1.0.0", 4 | "description": "Curated registry for people", 5 | "main": "index.js", 6 | "repository": "https://github.com/Proof-Of-Humanity/Proof-Of-Humanity", 7 | "author": "Kleros", 8 | "license": "MIT", 9 | "private": false, 10 | "scripts": { 11 | "prettify": "kathari prettify", 12 | "lint:sol": "kathari lint:sol", 13 | "lint:js": "kathari lint:js", 14 | "lint": "yarn run lint:sol", 15 | "test:ganache": "ganache-cli --gasLimit 8000000 --defaultBalanceEther 10000 --quiet &", 16 | "test:truffle": "truffle test", 17 | "test": "run-p test:*", 18 | "cz": "kathari cz", 19 | "build": "truffle compile", 20 | "release": "standard-version" 21 | }, 22 | "files": [ 23 | "build/contracts/*.json" 24 | ], 25 | "commitlint": { 26 | "extends": [ 27 | "@commitlint/config-conventional" 28 | ] 29 | }, 30 | "husky": { 31 | "hooks": { 32 | "pre-commit": "kathari precommit", 33 | "commit-msg": "kathari commitmsg" 34 | } 35 | }, 36 | "devDependencies": { 37 | "@kleros/kathari": "^0.25.0", 38 | "eth-gas-reporter": "^0.2.0", 39 | "ganache-cli": "^6.4.4", 40 | "husky": "^2.4.1", 41 | "jsonfile": "^5.0.0", 42 | "npm-run-all": "^4.1.5", 43 | "standard-version": "^7.0.0", 44 | "truffle": "^5.0.25" 45 | }, 46 | "dependencies": { 47 | "@kleros/erc-792": "^3.0.0", 48 | "@kleros/ethereum-libraries": "^1.0.0", 49 | "@openzeppelin/contracts": "^2.3.0", 50 | "chai": "^4.2.0", 51 | "openzeppelin-test-helpers": "^0.4.3", 52 | "solidity-bytes-utils": "^0.0.8", 53 | "solidity-rlp": "^2.0.1", 54 | "web3-utils": "^1.2.1" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /test/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DemocracyEarth/proof/bd6a86cd6873d25bfdd15a2ebe3a247922f7b683/test/.gitkeep -------------------------------------------------------------------------------- /test/proof-of-humanity.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ // Avoid the linter considering truffle elements as undef. 2 | const { BN, expectRevert, time } = require('openzeppelin-test-helpers') 3 | 4 | const ProofOfHumanity = artifacts.require('ProofOfHumanity') 5 | const Arbitrator = artifacts.require('EnhancedAppealableArbitrator') 6 | 7 | contract('ProofOfHumanity', function(accounts) { 8 | const governor = accounts[0] 9 | const requester = accounts[1] 10 | const requester2 = accounts[2] 11 | 12 | const challenger1 = accounts[3] 13 | const challenger2 = accounts[4] 14 | 15 | const voucher1 = accounts[5] 16 | const voucher2 = accounts[6] 17 | const voucher3 = accounts[7] 18 | const other = accounts[8] 19 | 20 | const MULTIPLIER_DIVISOR = 10000 21 | const arbitratorExtraData = '0x85' 22 | const arbitrationCost = 1000 23 | const submissionBaseDeposit = 5000 24 | const submissionDuration = 86400 25 | const challengePeriodDuration = 600 26 | const renewalPeriodDuration = 6000 27 | const nbVouches = 2 28 | 29 | const appealTimeOut = 180 30 | 31 | const sharedStakeMultiplier = 5000 32 | const winnerStakeMultiplier = 2000 33 | const loserStakeMultiplier = 8000 34 | 35 | const registrationMetaEvidence = 'registrationMetaEvidence.json' 36 | const clearingMetaEvidence = 'clearingMetaEvidence.json' 37 | 38 | const gasPrice = 8000000 39 | 40 | let arbitrator 41 | let proofH 42 | let requesterTotalCost 43 | 44 | beforeEach('initialize the contract', async function() { 45 | arbitrator = await Arbitrator.new( 46 | arbitrationCost, 47 | governor, 48 | arbitratorExtraData, 49 | appealTimeOut, 50 | { from: governor } 51 | ) 52 | 53 | await arbitrator.changeArbitrator(arbitrator.address) 54 | await arbitrator.createDispute(3, arbitratorExtraData, { 55 | from: other, 56 | value: arbitrationCost 57 | }) // Create a dispute so the index in tests will not be a default value. 58 | 59 | proofH = await ProofOfHumanity.new( 60 | arbitrator.address, 61 | arbitratorExtraData, 62 | registrationMetaEvidence, 63 | clearingMetaEvidence, 64 | submissionBaseDeposit, 65 | submissionDuration, 66 | renewalPeriodDuration, 67 | challengePeriodDuration, 68 | sharedStakeMultiplier, 69 | winnerStakeMultiplier, 70 | loserStakeMultiplier, 71 | nbVouches, 72 | { from: governor } 73 | ) 74 | 75 | await proofH.addSubmissionManually(voucher1, '', { from: governor }) 76 | await proofH.addSubmissionManually(voucher2, '', { from: governor }) 77 | await proofH.addSubmissionManually(voucher3, '', { from: governor }) 78 | 79 | requesterTotalCost = arbitrationCost + submissionBaseDeposit // Total sum: 1000 + 5000 = 6000 80 | }) 81 | 82 | it('Should set the correct values in constructor', async () => { 83 | assert.equal(await proofH.governor(), governor) 84 | assert.equal(await proofH.submissionBaseDeposit(), submissionBaseDeposit) 85 | assert.equal(await proofH.submissionDuration(), submissionDuration) 86 | assert.equal(await proofH.renewalPeriodDuration(), renewalPeriodDuration) 87 | assert.equal( 88 | await proofH.challengePeriodDuration(), 89 | challengePeriodDuration 90 | ) 91 | assert.equal(await proofH.sharedStakeMultiplier(), sharedStakeMultiplier) 92 | assert.equal(await proofH.winnerStakeMultiplier(), winnerStakeMultiplier) 93 | assert.equal(await proofH.loserStakeMultiplier(), loserStakeMultiplier) 94 | assert.equal(await proofH.requiredNumberOfVouches(), nbVouches) 95 | 96 | const arbitratorData = await proofH.arbitratorDataList(0) 97 | assert.equal(arbitratorData[0], arbitrator.address) 98 | assert.equal(arbitratorData[1], 0) 99 | assert.equal(arbitratorData[2], arbitratorExtraData) 100 | assert.equal(await proofH.getArbitratorDataListCount(), 1) 101 | }) 102 | 103 | it('Should set correct values in manually added submissions', async () => { 104 | const submission1 = await proofH.getSubmissionInfo(voucher1) 105 | assert.equal( 106 | submission1[0].toNumber(), 107 | 0, 108 | 'First submission has incorrect status' 109 | ) 110 | assert.equal( 111 | submission1[6].toNumber(), 112 | 1, 113 | 'First submission has incorrect number of requests' 114 | ) 115 | const request1 = await proofH.getRequestInfo(voucher1, 0) 116 | assert.equal( 117 | request1[1], 118 | true, 119 | 'The request of the first submission should be resolved' 120 | ) 121 | assert.equal( 122 | request1[6].toNumber(), 123 | 0, 124 | 'The request of the first submission has incorrect arbitrator data ID' 125 | ) 126 | assert.equal( 127 | submission1[3].toNumber(), 128 | 0, 129 | 'Incorrect index of the first submission' 130 | ) 131 | assert.equal(submission1[4], true, 'First submission should be registered') 132 | 133 | // Check the data of the 2nd submission as well. 134 | let submission2 = await proofH.getSubmissionInfo(voucher2) 135 | assert.equal( 136 | submission2[0].toNumber(), 137 | 0, 138 | 'Second submission has incorrect status' 139 | ) 140 | assert.equal( 141 | submission2[6].toNumber(), 142 | 1, 143 | 'Second submission has incorrect number of requests' 144 | ) 145 | const request2 = await proofH.getRequestInfo(voucher2, 0) 146 | assert.equal( 147 | request2[1], 148 | true, 149 | 'The request of the second submission should be resolved' 150 | ) 151 | assert.equal( 152 | request1[6].toNumber(), 153 | 0, 154 | 'The request of the second submission has incorrect arbitrator data ID' 155 | ) 156 | 157 | assert.equal( 158 | submission2[3].toNumber(), 159 | 1, 160 | 'Incorrect index of the second submission' 161 | ) 162 | assert.equal(submission2[4], true, 'Second submission should be registered') 163 | 164 | // There is no point in checking the data of the 3rd submission in detail. 165 | assert.equal( 166 | (await proofH.submissionCount()).toNumber(), 167 | 3, 168 | 'Incorrect submission count after manual registration' 169 | ) 170 | 171 | await proofH.removeSubmissionManually(voucher2, { from: governor }) 172 | submission2 = await proofH.getSubmissionInfo(voucher2) 173 | assert.equal( 174 | submission2[4], 175 | false, 176 | 'Second submission should not be registered after manual removal' 177 | ) 178 | 179 | await expectRevert( 180 | proofH.addSubmissionManually(voucher2, '', { from: governor }), 181 | 'Submission already been created' 182 | ) 183 | }) 184 | 185 | it('Should set correct values after creating a request to add new submission', async () => { 186 | // Change metaevidence so arbitrator data ID is not 0 187 | await proofH.changeMetaEvidence('1', '2', { from: governor }) 188 | 189 | const oldBalance = await web3.eth.getBalance(requester) 190 | const txAddSubmission = await proofH.addSubmission('evidence1', { 191 | from: requester, 192 | gasPrice: gasPrice, 193 | value: 1e18 194 | }) 195 | const txFee = txAddSubmission.receipt.gasUsed * gasPrice 196 | 197 | const submission = await proofH.getSubmissionInfo(requester) 198 | assert.equal(submission[0].toNumber(), 1, 'Submission has incorrect status') 199 | assert.equal( 200 | submission[6].toNumber(), 201 | 1, 202 | 'Submission has incorrect number of requests' 203 | ) 204 | assert.equal( 205 | submission[4], 206 | false, 207 | 'Submission should not be registered yet' 208 | ) 209 | 210 | const request = await proofH.getRequestInfo(requester, 0) 211 | assert.equal( 212 | request[6].toNumber(), 213 | 1, 214 | 'Arbitrator data ID was not set up properly' 215 | ) 216 | assert.equal(request[7], requester, 'Requester was not set up properly') 217 | 218 | const arbitratorData = await proofH.arbitratorDataList(1) 219 | assert.equal( 220 | arbitratorData[0], 221 | arbitrator.address, 222 | 'Arbitrator was not set up properly' 223 | ) 224 | assert.equal( 225 | arbitratorData[2], 226 | arbitratorExtraData, 227 | 'Extra data was not set up properly' 228 | ) 229 | 230 | const round = await proofH.getRoundInfo(requester, 0, 0, 0) 231 | assert.equal( 232 | round[1][1].toNumber(), 233 | 6000, 234 | 'Requester paidFees has not been registered correctly' 235 | ) 236 | assert.equal( 237 | round[2][1], 238 | true, 239 | 'Should register that requester paid his fees' 240 | ) 241 | assert.equal( 242 | round[3].toNumber(), 243 | 6000, 244 | 'FeeRewards has not been registered correctly' 245 | ) 246 | 247 | const contribution = await proofH.getContributions( 248 | requester, 249 | 0, 250 | 0, 251 | 0, 252 | requester 253 | ) 254 | assert.equal( 255 | contribution[1].toNumber(), 256 | 6000, 257 | 'Requester contribution has not been registered correctly' 258 | ) 259 | 260 | const newBalance = await web3.eth.getBalance(requester) 261 | assert( 262 | new BN(newBalance).eq( 263 | new BN(oldBalance).sub(new BN(requesterTotalCost).add(new BN(txFee))) 264 | ), 265 | 'The requester has incorrect balance after making a submission' 266 | ) 267 | 268 | assert.equal( 269 | txAddSubmission.logs[0].event, 270 | 'Evidence', 271 | 'The event has not been created' 272 | ) 273 | assert.equal( 274 | txAddSubmission.logs[0].args._arbitrator, 275 | arbitrator.address, 276 | 'The event has wrong arbitrator address' 277 | ) 278 | const evidenceGroupID = parseInt(requester, 16) 279 | assert.equal( 280 | txAddSubmission.logs[0].args._evidenceGroupID, 281 | evidenceGroupID, 282 | 'The event has wrong evidence group ID' 283 | ) 284 | assert.equal( 285 | txAddSubmission.logs[0].args._party, 286 | requester, 287 | 'The event has wrong requester address' 288 | ) 289 | assert.equal( 290 | txAddSubmission.logs[0].args._evidence, 291 | 'evidence1', 292 | 'The event has incorrect evidence' 293 | ) 294 | 295 | await expectRevert( 296 | proofH.addSubmission('', { from: requester, value: 1e18 }), 297 | 'Wrong status' 298 | ) 299 | 300 | // Check that manual actions are not possible as well. 301 | await expectRevert( 302 | proofH.addSubmissionManually(requester, '', { from: governor }), 303 | 'Submission already been created' 304 | ) 305 | await expectRevert( 306 | proofH.removeSubmissionManually(requester, { from: governor }), 307 | 'Wrong status' 308 | ) 309 | }) 310 | 311 | it('Should correctly fund the new submission', async () => { 312 | await proofH.addSubmission('evidence1', { from: requester, value: 200 }) 313 | 314 | let round = await proofH.getRoundInfo(requester, 0, 0, 0) 315 | assert.equal( 316 | round[1][1].toNumber(), 317 | 200, 318 | 'PaidFees has not been registered correctly' 319 | ) 320 | assert.equal( 321 | round[2][1], 322 | false, 323 | 'Should not register that the requester paid his fees fully' 324 | ) 325 | assert.equal( 326 | round[3].toNumber(), 327 | 200, 328 | 'FeeRewards has not been registered correctly' 329 | ) 330 | 331 | let contribution = await proofH.getContributions( 332 | requester, 333 | 0, 334 | 0, 335 | 0, 336 | requester 337 | ) 338 | assert.equal( 339 | contribution[1].toNumber(), 340 | 200, 341 | 'Requester contribution has not been registered correctly' 342 | ) 343 | 344 | // Let the requester fund the submission once more to see if the sum of both payments is correct. 345 | await proofH.fundSubmission(requester, { from: requester, value: 300 }) 346 | 347 | round = await proofH.getRoundInfo(requester, 0, 0, 0) 348 | assert.equal( 349 | round[1][1].toNumber(), 350 | 500, 351 | 'PaidFees has not been registered correctly after the 2nd payment of the requester' 352 | ) 353 | assert.equal( 354 | round[2][1], 355 | false, 356 | 'Should not register that requester paid his fees fully after the 2nd payment of the requester' 357 | ) 358 | assert.equal( 359 | round[3].toNumber(), 360 | 500, 361 | 'FeeRewards has not been registered correctly after the 2nd payment of the requester' 362 | ) 363 | 364 | contribution = await proofH.getContributions(requester, 0, 0, 0, requester) 365 | assert.equal( 366 | contribution[1].toNumber(), 367 | 500, 368 | 'Requester contribution has not been registered correctly after the 2nd payment of the requester' 369 | ) 370 | 371 | // Check that the payment of the first crowdfunder has been registered correctly. 372 | await proofH.fundSubmission(requester, { from: voucher1, value: 5000 }) 373 | 374 | round = await proofH.getRoundInfo(requester, 0, 0, 0) 375 | assert.equal( 376 | round[1][1].toNumber(), 377 | 5500, 378 | 'PaidFees has not been registered correctly after the first crowdfunder' 379 | ) 380 | assert.equal( 381 | round[2][1], 382 | false, 383 | 'Should not register that the requester paid his fees fully after the first crowdfunder' 384 | ) 385 | assert.equal( 386 | round[3].toNumber(), 387 | 5500, 388 | 'FeeRewards has not been registered correctly after the first crowdfunder' 389 | ) 390 | 391 | contribution = await proofH.getContributions(requester, 0, 0, 0, voucher1) 392 | assert.equal( 393 | contribution[1].toNumber(), 394 | 5000, 395 | 'First crowdfunder contribution has not been registered correctly' 396 | ) 397 | 398 | // Check the second crowdfunder. 399 | await proofH.fundSubmission(requester, { from: other, value: 1e18 }) 400 | 401 | round = await proofH.getRoundInfo(requester, 0, 0, 0) 402 | assert.equal( 403 | round[1][1].toNumber(), 404 | requesterTotalCost, 405 | 'PaidFees has not been registered correctly after the second crowdfunder' 406 | ) 407 | assert.equal( 408 | round[2][1], 409 | true, 410 | 'Should register that the requester paid his fees fully after the second crowdfunder' 411 | ) 412 | assert.equal( 413 | round[3].toNumber(), 414 | requesterTotalCost, 415 | 'FeeRewards has not been registered correctly after the second crowdfunder' 416 | ) 417 | 418 | contribution = await proofH.getContributions(requester, 0, 0, 0, other) 419 | assert.equal( 420 | contribution[1].toNumber(), 421 | 500, 422 | 'Second crowdfunder contribution has not been registered correctly' 423 | ) 424 | 425 | // Check that already registered or absent submission can't be funded. 426 | await expectRevert( 427 | proofH.fundSubmission(voucher1, { from: voucher1 }), 428 | 'Wrong status' 429 | ) 430 | await expectRevert( 431 | proofH.fundSubmission(other, { from: other }), 432 | 'Wrong status' 433 | ) 434 | }) 435 | 436 | it('Should set correct values after creating a request to remove a submission', async () => { 437 | await expectRevert( 438 | proofH.removeSubmission(voucher1, 'evidence1', { 439 | from: requester, 440 | value: requesterTotalCost - 1 441 | }), 442 | 'You must fully fund your side' 443 | ) 444 | 445 | await proofH.removeSubmission(voucher1, 'evidence1', { 446 | from: requester, 447 | value: requesterTotalCost + 1 448 | }) // Overpay a little to see if the registered payment is correct. 449 | 450 | const submission = await proofH.getSubmissionInfo(voucher1) 451 | assert.equal(submission[0].toNumber(), 3, 'Submission has incorrect status') 452 | assert.equal( 453 | submission[6].toNumber(), 454 | 2, 455 | 'Submission has incorrect number of requests' 456 | ) 457 | assert.equal(submission[4], true, 'Submission should still be registered') 458 | 459 | const round = await proofH.getRoundInfo(voucher1, 1, 0, 0) 460 | assert.equal( 461 | round[1][1].toNumber(), 462 | requesterTotalCost, 463 | 'PaidFees has not been registered correctly' 464 | ) 465 | assert.equal( 466 | round[2][1], 467 | true, 468 | 'Should register that the requester paid his fees fully' 469 | ) 470 | assert.equal( 471 | round[3].toNumber(), 472 | requesterTotalCost, 473 | 'FeeRewards has not been registered correctly' 474 | ) 475 | 476 | const contribution = await proofH.getContributions( 477 | voucher1, 478 | 1, 479 | 0, 480 | 0, 481 | requester 482 | ) 483 | assert.equal( 484 | contribution[1].toNumber(), 485 | requesterTotalCost, 486 | 'Requester contribution has not been registered correctly' 487 | ) 488 | 489 | // Check that it's not possible to make a removal request for a submission that is not registered. 490 | await expectRevert( 491 | proofH.removeSubmission(other, 'evidence1', { 492 | from: requester, 493 | value: requesterTotalCost 494 | }), 495 | 'Wrong status' 496 | ) 497 | 498 | await proofH.addSubmission('evidence1', { 499 | from: other, 500 | value: requesterTotalCost 501 | }) 502 | await expectRevert( 503 | proofH.removeSubmission(other, 'evidence1', { 504 | from: requester, 505 | value: requesterTotalCost 506 | }), 507 | 'Wrong status' 508 | ) 509 | 510 | // Check that it's not possible to make a request during renewal period. 511 | await time.increase(submissionDuration - renewalPeriodDuration) 512 | await expectRevert( 513 | proofH.removeSubmission(voucher2, 'evidence1', { 514 | from: requester, 515 | value: requesterTotalCost 516 | }), 517 | "Can't remove during renewal" 518 | ) 519 | }) 520 | 521 | it('Should not be possible to reapply before renewal time or with the wrong status', async () => { 522 | await expectRevert( 523 | proofH.reapplySubmission('.json', { from: voucher1 }), 524 | "Can't reapply yet" 525 | ) 526 | await time.increase(submissionDuration - renewalPeriodDuration) 527 | 528 | await proofH.reapplySubmission('.json', { from: voucher1 }) 529 | 530 | await expectRevert( 531 | proofH.addVouch(voucher1, { from: voucher1 }), 532 | "Can't vouch for yourself" 533 | ) 534 | 535 | const submission = await proofH.getSubmissionInfo(voucher1) 536 | assert.equal(submission[0].toNumber(), 1, 'Submission has incorrect status') 537 | assert.equal( 538 | submission[6].toNumber(), 539 | 2, 540 | 'Submission has incorrect number of requests' 541 | ) 542 | assert.equal(submission[4], true, 'Submission should still be registered') 543 | 544 | await expectRevert( 545 | proofH.reapplySubmission('.json', { from: other }), 546 | 'Wrong status' 547 | ) 548 | 549 | // Check that it's not possible to reapply 2nd time. 550 | await expectRevert( 551 | proofH.reapplySubmission('.json', { from: voucher1 }), 552 | 'Wrong status' 553 | ) 554 | }) 555 | 556 | it('Should correctly store vouches and change vouching state', async () => { 557 | await expectRevert( 558 | proofH.addVouch(requester, { from: voucher1 }), 559 | 'Wrong status' 560 | ) 561 | 562 | await proofH.addSubmission('evidence1', { from: requester }) 563 | 564 | await expectRevert( 565 | proofH.addVouch(requester, { from: other }), 566 | 'No right to vouch' 567 | ) 568 | 569 | const txVouchAdd = await proofH.addVouch(requester, { from: voucher1 }) 570 | 571 | await expectRevert( 572 | proofH.addVouch(requester, { from: voucher1 }), 573 | 'Already vouched for this' 574 | ) 575 | 576 | let isVouched = await proofH.vouches(voucher1, requester) 577 | assert.equal( 578 | isVouched, 579 | true, 580 | 'Should register the vouch for the submission' 581 | ) 582 | // Check vouching events. 583 | assert.equal( 584 | txVouchAdd.logs[0].event, 585 | 'VouchAdded', 586 | 'The event VouchAdded has not been created' 587 | ) 588 | assert.equal( 589 | txVouchAdd.logs[0].args._submissionID, 590 | requester, 591 | 'The event VouchAdded has wrong submission address' 592 | ) 593 | assert.equal( 594 | txVouchAdd.logs[0].args._voucher, 595 | voucher1, 596 | 'The event VouchAdded has wrong voucher address' 597 | ) 598 | // Check that the vouch can be removed successfully and then add it again. 599 | const txVouchRemove = await proofH.removeVouch(requester, { 600 | from: voucher1 601 | }) 602 | 603 | await expectRevert( 604 | proofH.removeVouch(requester, { from: voucher1 }), 605 | 'No vouch to remove' 606 | ) 607 | 608 | isVouched = await proofH.vouches(voucher1, requester) 609 | assert.equal(isVouched, false, 'The vouch should be removed') 610 | 611 | assert.equal( 612 | txVouchRemove.logs[0].event, 613 | 'VouchRemoved', 614 | 'The event VouchRemoved has not been created' 615 | ) 616 | assert.equal( 617 | txVouchRemove.logs[0].args._submissionID, 618 | requester, 619 | 'The event VouchRemoved has wrong submission address' 620 | ) 621 | assert.equal( 622 | txVouchAdd.logs[0].args._voucher, 623 | voucher1, 624 | 'The event VouchRemoved has wrong voucher address' 625 | ) 626 | 627 | await proofH.addVouch(requester, { from: voucher1 }) 628 | await proofH.addVouch(requester, { from: voucher2 }) 629 | 630 | await expectRevert( 631 | proofH.changeStateToPending(requester, [voucher1, voucher2], { 632 | from: governor 633 | }), 634 | 'Requester is not funded' 635 | ) 636 | 637 | await proofH.fundSubmission(requester, { 638 | from: requester, 639 | value: requesterTotalCost 640 | }) 641 | // Deliberately add "bad" vouchers to see if the count is correct. 642 | await proofH.changeStateToPending( 643 | requester, 644 | [governor, voucher1, challenger1, voucher2, other], 645 | { from: governor } 646 | ) 647 | 648 | const submission = await proofH.getSubmissionInfo(requester) 649 | assert.equal(submission[0].toNumber(), 2, 'Submission has incorrect status') 650 | 651 | const voucher1Info = await proofH.getSubmissionInfo(voucher1) 652 | assert.equal(voucher1Info[5], true, 'Did not register the first vouch') 653 | const voucher2Info = await proofH.getSubmissionInfo(voucher2) 654 | assert.equal(voucher2Info[5], true, 'Did not register the second vouch') 655 | 656 | const storedVouches = ( 657 | await proofH.getNumberOfVouches(requester, 0) 658 | ).toNumber() 659 | assert.equal( 660 | storedVouches, 661 | 2, 662 | 'Incorrect number of vouches stored in submission request' 663 | ) 664 | // Check that the vouch can no longer be removed after the state has changed. 665 | await expectRevert( 666 | proofH.removeVouch(requester, { from: voucher1 }), 667 | 'Wrong status' 668 | ) 669 | }) 670 | 671 | it('Check that invalid vouches are not counted', async () => { 672 | // Change required number of vouches to 1 to make checks more transparent 673 | await proofH.changeRequiredNumberOfVouches(1, { from: governor }) 674 | 675 | await proofH.addSubmission('evidence1', { 676 | from: requester, 677 | value: requesterTotalCost 678 | }) 679 | 680 | // Empty array of vouchers. 681 | await expectRevert( 682 | proofH.changeStateToPending(requester, [], { from: governor }), 683 | 'Not enough valid vouches' 684 | ) 685 | // Array with voucher who didn't vouch. 686 | await expectRevert( 687 | proofH.changeStateToPending(requester, [voucher1], { from: governor }), 688 | 'Not enough valid vouches' 689 | ) 690 | // Voucher who already vouched for a different submission. 691 | await proofH.addSubmission('evidence1', { 692 | from: requester2, 693 | value: requesterTotalCost 694 | }) 695 | await proofH.addVouch(requester, { from: voucher2 }) 696 | await proofH.addVouch(requester2, { from: voucher2 }) 697 | await proofH.changeStateToPending(requester2, [voucher2], { 698 | from: governor 699 | }) 700 | await expectRevert( 701 | proofH.changeStateToPending(requester, [voucher2], { from: governor }), 702 | 'Not enough valid vouches' 703 | ) 704 | // Voucher whose submission time has expired. 705 | await proofH.changeSubmissionDuration(10, { from: governor }) 706 | await time.increase(10) 707 | 708 | await expectRevert( 709 | proofH.addVouch(requester, { from: voucher1 }), 710 | 'No right to vouch' 711 | ) 712 | 713 | // Change the submission time and nbVouches back to do another checks. 714 | await proofH.changeSubmissionDuration(submissionDuration, { 715 | from: governor 716 | }) 717 | await proofH.changeRequiredNumberOfVouches(nbVouches, { from: governor }) 718 | await proofH.addVouch(requester, { from: voucher1 }) 719 | 720 | // Check that the voucher can't be duplicated. 721 | await expectRevert( 722 | proofH.changeStateToPending(requester, [voucher1, voucher1], { 723 | from: governor 724 | }), 725 | 'Not enough valid vouches' 726 | ) 727 | }) 728 | 729 | it('Should not use more vouches than needed', async () => { 730 | await proofH.addSubmission('evidence1', { 731 | from: requester, 732 | value: requesterTotalCost 733 | }) 734 | await proofH.addVouch(requester, { from: voucher1 }) 735 | await proofH.addVouch(requester, { from: voucher2 }) 736 | await proofH.addVouch(requester, { from: voucher3 }) 737 | await proofH.changeStateToPending( 738 | requester, 739 | [voucher1, voucher2, voucher3], 740 | { from: governor } 741 | ) 742 | const voucher1Info = await proofH.getSubmissionInfo(voucher1) 743 | assert.equal( 744 | voucher1Info[5], 745 | true, 746 | 'First voucher should be marked as used' 747 | ) 748 | const voucher2Info = await proofH.getSubmissionInfo(voucher2) 749 | assert.equal( 750 | voucher2Info[5], 751 | true, 752 | 'Second voucher should be marked as used' 753 | ) 754 | const voucher3Info = await proofH.getSubmissionInfo(voucher3) 755 | assert.equal(voucher3Info[5], false, 'Third voucher should not be used') 756 | }) 757 | 758 | it('Should set correct values and create a dispute after the submission is challenged', async () => { 759 | // Check that the submission with the wrong status can't be challenged. 760 | await expectRevert( 761 | proofH.challengeRequest( 762 | voucher1, 763 | 2, 764 | '0x0000000000000000000000000000000000000000', 765 | 'evidence2', 766 | { from: challenger1, value: 1e18 } 767 | ), 768 | 'Wrong status' 769 | ) 770 | await expectRevert( 771 | proofH.challengeRequest( 772 | requester, 773 | 2, 774 | '0x0000000000000000000000000000000000000000', 775 | 'evidence2', 776 | { from: challenger1, value: 1e18 } 777 | ), 778 | 'Wrong status' 779 | ) 780 | 781 | await proofH.addSubmission('', { 782 | from: requester, 783 | value: requesterTotalCost 784 | }) 785 | await proofH.addVouch(requester, { from: voucher1 }) 786 | await proofH.addVouch(requester, { from: voucher2 }) 787 | 788 | await expectRevert( 789 | proofH.challengeRequest( 790 | requester, 791 | 2, 792 | '0x0000000000000000000000000000000000000000', 793 | 'evidence2', 794 | { from: challenger1, value: 1e18 } 795 | ), 796 | 'Wrong status' 797 | ) 798 | 799 | await proofH.changeStateToPending(requester, [voucher1, voucher2], { 800 | from: governor 801 | }) 802 | 803 | // Check the rest of the require statements as well. 804 | await expectRevert( 805 | proofH.challengeRequest( 806 | requester, 807 | 0, 808 | '0x0000000000000000000000000000000000000000', 809 | 'evidence2', 810 | { from: challenger1, value: 1e18 } 811 | ), 812 | 'Reason must be specified' 813 | ) 814 | await expectRevert( 815 | proofH.challengeRequest(requester, 2, voucher1, 'evidence2', { 816 | from: challenger1, 817 | value: 1e18 818 | }), 819 | 'DuplicateID must be empty' 820 | ) 821 | await expectRevert( 822 | proofH.challengeRequest( 823 | requester, 824 | 2, 825 | '0x0000000000000000000000000000000000000000', 826 | 'evidence2', 827 | { from: challenger1, value: arbitrationCost - 1 } 828 | ), 829 | 'You must fully fund your side' 830 | ) 831 | 832 | const oldBalance = await web3.eth.getBalance(challenger1) 833 | // Deliberately overpay to see if the payment is registered correctly 834 | txChallenge = await proofH.challengeRequest( 835 | requester, 836 | 2, 837 | '0x0000000000000000000000000000000000000000', 838 | 'evidence2', 839 | { from: challenger1, gasPrice: gasPrice, value: 1e18 } 840 | ) 841 | const newBalance = await web3.eth.getBalance(challenger1) 842 | const txFee = txChallenge.receipt.gasUsed * gasPrice 843 | 844 | // Check that the request can't be challenged again with another reason. 845 | await expectRevert( 846 | proofH.challengeRequest( 847 | requester, 848 | 1, 849 | '0x0000000000000000000000000000000000000000', 850 | 'evidence2', 851 | { from: challenger1, value: 1e18 } 852 | ), 853 | 'The request is disputed' 854 | ) 855 | await expectRevert( 856 | proofH.challengeRequest(requester, 3, voucher1, 'evidence2', { 857 | from: challenger1, 858 | value: 1e18 859 | }), 860 | 'Another reason is active' 861 | ) 862 | 863 | assert( 864 | new BN(newBalance).eq( 865 | new BN(oldBalance).sub(new BN(arbitrationCost).add(new BN(txFee))) 866 | ), 867 | 'The challenger has incorrect balance after making a submission' 868 | ) 869 | 870 | const request = await proofH.getRequestInfo(requester, 0) 871 | assert.equal(request[0], true, 'The request should be disputed') 872 | // The number of challenges is incremented beforehand so it should be 2. 873 | assert.equal( 874 | request[3].toNumber(), 875 | 2, 876 | 'The current reason of the request is incorrect' 877 | ) 878 | assert.equal( 879 | request[4].toNumber(), 880 | 1, 881 | 'The number of parallel disputes of the request is incorrect' 882 | ) 883 | assert.equal( 884 | request[5].toNumber(), 885 | 2, 886 | 'The number of challenges of the request is incorrect' 887 | ) 888 | assert.equal(request[9].toNumber(), 2, 'Incorrect reasons bitmap value') 889 | 890 | const challengeInfo = await proofH.getChallengeInfo(requester, 0, 0) 891 | assert.equal( 892 | challengeInfo[0].toNumber(), 893 | 2, 894 | 'Incorrect number of rounds after challenge' 895 | ) 896 | assert.equal( 897 | challengeInfo[1], 898 | challenger1, 899 | 'Challenger not set up properly' 900 | ) 901 | assert.equal( 902 | challengeInfo[2].toNumber(), 903 | 1, 904 | 'Incorrect dispute ID of the challenge' 905 | ) 906 | assert.equal(challengeInfo[4].toNumber(), 0, 'Duplicate index should be 0') 907 | const disputeData = await proofH.arbitratorDisputeIDToDisputeData( 908 | arbitrator.address, 909 | 1 910 | ) 911 | assert.equal(disputeData[0].toNumber(), 0, 'Incorrect challengeID') 912 | assert.equal( 913 | disputeData[1], 914 | requester, 915 | 'Incorrect submission ID stored in disputeData struct' 916 | ) 917 | 918 | let round = await proofH.getRoundInfo(requester, 0, 0, 0) 919 | assert.equal( 920 | round[1][2].toNumber(), 921 | 1000, 922 | 'Challenger paidFees has not been registered correctly' 923 | ) 924 | assert.equal( 925 | round[2][2], 926 | true, 927 | 'Should register that challenger paid his fees' 928 | ) 929 | assert.equal( 930 | round[3].toNumber(), 931 | 6000, // It should stay the same because the value of the deposit get subtracted when the dispute is created. 932 | 'Incorrect feeRewards value after challenge' 933 | ) 934 | 935 | // Also briefly check the round that was created beforehand for the new challenge. 936 | round = await proofH.getRoundInfo(requester, 0, 1, 0) 937 | assert.equal( 938 | round[3].toNumber(), 939 | 0, 940 | 'FeeRewards should be empty for the new challenge' 941 | ) 942 | 943 | const dispute = await arbitrator.disputes(1) 944 | assert.equal(dispute[0], proofH.address, 'Arbitrable not set up properly') 945 | assert.equal( 946 | dispute[1].toNumber(), 947 | 2, 948 | 'Number of choices not set up properly' 949 | ) 950 | 951 | assert.equal( 952 | txChallenge.logs[1].event, 953 | 'Dispute', 954 | 'The Dispute event has not been created' 955 | ) 956 | assert.equal( 957 | txChallenge.logs[1].args._arbitrator, 958 | arbitrator.address, 959 | 'The Dispute event has wrong arbitrator address' 960 | ) 961 | assert.equal( 962 | txChallenge.logs[1].args._disputeID.toNumber(), 963 | 1, 964 | 'The Dispute event has wrong dispute ID' 965 | ) 966 | assert.equal( 967 | txChallenge.logs[1].args._metaEvidenceID.toNumber(), 968 | 0, 969 | 'The Dispute event has wrong metaevidence ID' 970 | ) 971 | const evidenceGroupID = parseInt(requester, 16) 972 | assert.equal( 973 | txChallenge.logs[1].args._evidenceGroupID, 974 | evidenceGroupID, 975 | 'The Dispute event has wrong evidence group ID' 976 | ) 977 | assert.equal( 978 | txChallenge.logs[2].event, 979 | 'Evidence', 980 | 'The Evidence event has not been created' 981 | ) 982 | assert.equal( 983 | txChallenge.logs[2].args._arbitrator, 984 | arbitrator.address, 985 | 'The Evidence event has wrong arbitrator address' 986 | ) 987 | assert.equal( 988 | txChallenge.logs[2].args._evidenceGroupID, 989 | evidenceGroupID, 990 | 'The Evidence event has wrong evidence group ID' 991 | ) 992 | assert.equal( 993 | txChallenge.logs[2].args._party, 994 | challenger1, 995 | 'The Evidence event has wrong challenger address' 996 | ) 997 | assert.equal( 998 | txChallenge.logs[2].args._evidence, 999 | 'evidence2', 1000 | 'The Evidence event has incorrect evidence' 1001 | ) 1002 | // Check that the request can't just be executed after challenge. 1003 | await time.increase(challengePeriodDuration + 1) 1004 | await expectRevert( 1005 | proofH.executeRequest(requester, { from: governor }), 1006 | 'The request is disputed' 1007 | ) 1008 | }) 1009 | 1010 | it('Should not be possible to challenge after timeout', async () => { 1011 | await proofH.addSubmission('', { 1012 | from: requester, 1013 | value: requesterTotalCost 1014 | }) 1015 | await proofH.addVouch(requester, { from: voucher1 }) 1016 | await proofH.addVouch(requester, { from: voucher2 }) 1017 | 1018 | await proofH.changeStateToPending(requester, [voucher1, voucher2], { 1019 | from: governor 1020 | }) 1021 | await time.increase(challengePeriodDuration + 1) 1022 | await expectRevert( 1023 | proofH.challengeRequest( 1024 | requester, 1025 | 2, 1026 | '0x0000000000000000000000000000000000000000', 1027 | 'evidence2', 1028 | { from: challenger1, value: arbitrationCost } 1029 | ), 1030 | 'Time to challenge has passed' 1031 | ) 1032 | }) 1033 | 1034 | it('Should set correct values in parallel disputes', async () => { 1035 | await proofH.addSubmission('', { 1036 | from: requester, 1037 | value: requesterTotalCost 1038 | }) 1039 | await proofH.addVouch(requester, { from: voucher1 }) 1040 | await proofH.addVouch(requester, { from: voucher2 }) 1041 | 1042 | await proofH.changeStateToPending(requester, [voucher1, voucher2], { 1043 | from: governor 1044 | }) 1045 | 1046 | await expectRevert( 1047 | proofH.challengeRequest( 1048 | requester, 1049 | 3, 1050 | '0x0000000000000000000000000000000000000000', 1051 | '', 1052 | { from: challenger1, value: arbitrationCost } 1053 | ), 1054 | 'Wrong duplicate status' 1055 | ) 1056 | await expectRevert( 1057 | proofH.challengeRequest(requester, 3, requester, '', { 1058 | from: challenger1, 1059 | value: arbitrationCost 1060 | }), 1061 | "Can't be a duplicate of itself" 1062 | ) 1063 | 1064 | await proofH.challengeRequest(requester, 3, voucher2, '', { 1065 | from: challenger1, 1066 | value: arbitrationCost 1067 | }) 1068 | 1069 | await expectRevert( 1070 | proofH.challengeRequest(requester, 3, voucher2, '', { 1071 | from: challenger1, 1072 | value: arbitrationCost 1073 | }), 1074 | 'Duplicate address already used' 1075 | ) 1076 | assert.equal( 1077 | await proofH.checkRequestDuplicates(requester, 0, voucher2), 1078 | true, 1079 | 'The duplicate should be marked as used' 1080 | ) 1081 | 1082 | await proofH.challengeRequest(requester, 3, voucher3, '', { 1083 | from: challenger2, 1084 | value: arbitrationCost 1085 | }) 1086 | const request = await proofH.getRequestInfo(requester, 0) 1087 | assert.equal( 1088 | request[4].toNumber(), 1089 | 2, 1090 | 'The number of parallel disputes of the request is incorrect' 1091 | ) 1092 | // The number of challenges is incremented for the new potential challenge. 1093 | assert.equal( 1094 | request[5].toNumber(), 1095 | 3, 1096 | 'The number of challenges of the request is incorrect' 1097 | ) 1098 | assert.equal( 1099 | request[9].toNumber(), 1100 | 4, 1101 | 'The reasons bitmap of the request is incorrect' 1102 | ) 1103 | 1104 | const challengeInfo1 = await proofH.getChallengeInfo(requester, 0, 0) 1105 | assert.equal( 1106 | challengeInfo1[0].toNumber(), 1107 | 2, 1108 | 'Incorrect number of rounds of the first challenge' 1109 | ) 1110 | assert.equal( 1111 | challengeInfo1[1], 1112 | challenger1, 1113 | 'First challenger was not set up properly' 1114 | ) 1115 | assert.equal( 1116 | challengeInfo1[2].toNumber(), 1117 | 1, 1118 | 'Incorrect dispute ID of the first challenge' 1119 | ) 1120 | assert.equal( 1121 | challengeInfo1[4].toNumber(), 1122 | 1, 1123 | 'Duplicate index is incorrect for the first challenge' 1124 | ) 1125 | 1126 | const disputeData1 = await proofH.arbitratorDisputeIDToDisputeData( 1127 | arbitrator.address, 1128 | 1 1129 | ) 1130 | assert.equal( 1131 | disputeData1[0].toNumber(), 1132 | 0, 1133 | 'Incorrect challengeID of the first challenge' 1134 | ) 1135 | assert.equal( 1136 | disputeData1[1], 1137 | requester, 1138 | 'Challenged submission was not set up properly for the first challenge' 1139 | ) 1140 | 1141 | const challengeInfo2 = await proofH.getChallengeInfo(requester, 0, 1) 1142 | assert.equal( 1143 | challengeInfo2[0].toNumber(), 1144 | 2, 1145 | 'Incorrect number of rounds of the second challenge' 1146 | ) 1147 | assert.equal( 1148 | challengeInfo2[1], 1149 | challenger2, 1150 | 'Second challenger was not set up properly' 1151 | ) 1152 | assert.equal( 1153 | challengeInfo2[2].toNumber(), 1154 | 2, 1155 | 'Incorrect dispute ID of the second challenge' 1156 | ) 1157 | assert.equal( 1158 | challengeInfo2[4].toNumber(), 1159 | 2, 1160 | 'Duplicate index is incorrect for the second challenge' 1161 | ) 1162 | const disputeData2 = await proofH.arbitratorDisputeIDToDisputeData( 1163 | arbitrator.address, 1164 | 2 1165 | ) 1166 | assert.equal( 1167 | disputeData2[0].toNumber(), 1168 | 1, 1169 | 'Incorrect challengeID of the second challenge' 1170 | ) 1171 | assert.equal( 1172 | disputeData2[1], 1173 | requester, 1174 | 'Challenged submission was not set up properly for the second challenge' 1175 | ) 1176 | 1177 | let round = await proofH.getRoundInfo(requester, 0, 0, 0) 1178 | assert.equal( 1179 | round[3].toNumber(), 1180 | 6000, 1181 | 'Incorrect feeRewards value for the first challenge' 1182 | ) 1183 | round = await proofH.getRoundInfo(requester, 0, 1, 0) 1184 | assert.equal( 1185 | round[3].toNumber(), 1186 | 0, // The second challenge doesn't count the requester's payment, so feeRewards should stay 0. 1187 | 'Incorrect feeRewards value for the second challenge' 1188 | ) 1189 | }) 1190 | 1191 | it('Should set correct values when challenging a removal request', async () => { 1192 | // All checks for correct values have already been done in previous tests. Here just check conditions that are unique for this type of challenge. 1193 | await proofH.removeSubmission(voucher1, '', { 1194 | from: requester, 1195 | value: requesterTotalCost 1196 | }) 1197 | 1198 | await expectRevert( 1199 | proofH.challengeRequest( 1200 | voucher1, 1201 | 1, 1202 | '0x0000000000000000000000000000000000000000', 1203 | '', 1204 | { from: challenger1, value: arbitrationCost } 1205 | ), 1206 | 'Reason must be left empty' 1207 | ) 1208 | 1209 | await proofH.challengeRequest( 1210 | voucher1, 1211 | 0, 1212 | '0x0000000000000000000000000000000000000000', 1213 | '', 1214 | { from: challenger1, value: arbitrationCost } 1215 | ) 1216 | 1217 | const request = await proofH.getRequestInfo(voucher1, 1) 1218 | assert.equal( 1219 | request[3].toNumber(), 1220 | 0, 1221 | 'The current reason of the removal request should be 0' 1222 | ) 1223 | assert.equal( 1224 | request[5].toNumber(), 1225 | 2, 1226 | 'The number of challenges of the removal request is incorrect' 1227 | ) 1228 | assert.equal(request[9].toNumber(), 0, 'The reasons bitmap should be empty') 1229 | }) 1230 | 1231 | it('Should successfully execute a request if it has not been challenged', async () => { 1232 | await proofH.addSubmission('', { 1233 | from: requester, 1234 | value: requesterTotalCost 1235 | }) 1236 | await proofH.addVouch(requester, { from: voucher1 }) 1237 | await proofH.addVouch(requester, { from: voucher2 }) 1238 | 1239 | await proofH.changeStateToPending(requester, [voucher1, voucher2], { 1240 | from: governor 1241 | }) 1242 | 1243 | await expectRevert( 1244 | proofH.executeRequest(requester, { from: governor }), 1245 | "Can't execute yet" 1246 | ) 1247 | 1248 | await time.increase(challengePeriodDuration + 1) 1249 | 1250 | const oldBalance = await web3.eth.getBalance(requester) 1251 | await proofH.executeRequest(requester, { from: governor }) 1252 | const newBalance = await web3.eth.getBalance(requester) 1253 | 1254 | let submission = await proofH.getSubmissionInfo(requester) 1255 | assert.equal( 1256 | submission[0].toNumber(), 1257 | 0, 1258 | 'The submission should have a default status' 1259 | ) 1260 | assert.equal(submission[4], true, 'The submission should be registered') 1261 | 1262 | let request = await proofH.getRequestInfo(requester, 0) 1263 | assert.equal(request[1], true, 'The request should be resolved') 1264 | assert( 1265 | new BN(newBalance).eq(new BN(oldBalance).add(new BN(requesterTotalCost))), 1266 | 'The requester was not reimbursed correctly' 1267 | ) 1268 | 1269 | const contribution = await proofH.getContributions( 1270 | requester, 1271 | 0, 1272 | 0, 1273 | 0, 1274 | requester 1275 | ) 1276 | assert.equal( 1277 | contribution[1].toNumber(), 1278 | 0, 1279 | 'Contribution of the requester should be 0' 1280 | ) 1281 | // Check that it's not possible to execute two times in a row. 1282 | await expectRevert( 1283 | proofH.executeRequest(requester, { from: governor }), 1284 | 'Incorrect status.' 1285 | ) 1286 | 1287 | // Also check removal request. 1288 | await proofH.removeSubmission(requester, '', { 1289 | from: requester2, 1290 | value: requesterTotalCost 1291 | }) 1292 | await time.increase(challengePeriodDuration + 1) 1293 | 1294 | await proofH.executeRequest(requester, { from: governor }) 1295 | submission = await proofH.getSubmissionInfo(requester) 1296 | assert.equal( 1297 | submission[0].toNumber(), 1298 | 0, 1299 | 'The submission should have a default status after removal' 1300 | ) 1301 | assert.equal( 1302 | submission[4], 1303 | false, 1304 | 'The submission should not be registered after removal' 1305 | ) 1306 | request = await proofH.getRequestInfo(requester, 1) 1307 | assert.equal( 1308 | request[1], 1309 | true, 1310 | 'The request should be resolved after removal' 1311 | ) 1312 | }) 1313 | 1314 | it('Should demand correct appeal fees and register that appeal fee has been paid', async () => { 1315 | let roundInfo 1316 | await proofH.addSubmission('', { 1317 | from: requester, 1318 | value: requesterTotalCost 1319 | }) 1320 | await proofH.addVouch(requester, { from: voucher1 }) 1321 | await proofH.addVouch(requester, { from: voucher2 }) 1322 | 1323 | await proofH.changeStateToPending(requester, [voucher1, voucher2], { 1324 | from: governor 1325 | }) 1326 | await expectRevert.unspecified( 1327 | proofH.fundAppeal(requester, 0, 2, { from: challenger1, value: 1e18 }) // Check that can't appeal without dispute. 1328 | ) 1329 | 1330 | await proofH.challengeRequest( 1331 | requester, 1332 | 2, 1333 | '0x0000000000000000000000000000000000000000', 1334 | '', 1335 | { from: challenger1, value: arbitrationCost } 1336 | ) 1337 | 1338 | await arbitrator.giveRuling(1, 2) 1339 | 1340 | // Appeal fee is the same as arbitration fee for this arbitrator. 1341 | const loserAppealFee = 1342 | arbitrationCost + 1343 | (arbitrationCost * loserStakeMultiplier) / MULTIPLIER_DIVISOR // 1000 + 1000 * 0.8 = 1800 1344 | 1345 | await expectRevert.unspecified( 1346 | proofH.fundAppeal(requester, 0, 0, { 1347 | from: challenger1, 1348 | value: loserAppealFee 1349 | }) // Check that not possible to fund 0 side. 1350 | ) 1351 | 1352 | // Deliberately overpay to check that only required fee amount will be registered. 1353 | await proofH.fundAppeal(requester, 0, 1, { from: requester, value: 1e18 }) 1354 | 1355 | // Fund appeal again to see if it doesn't cause anything. 1356 | await proofH.fundAppeal(requester, 0, 1, { from: requester, value: 1e18 }) 1357 | 1358 | roundInfo = await proofH.getRoundInfo(requester, 0, 0, 1) // Appeal rounds start with 1. 1359 | 1360 | assert.equal( 1361 | roundInfo[1][1].toNumber(), 1362 | 1800, 1363 | 'Registered fee of the requester is incorrect' 1364 | ) 1365 | assert.equal( 1366 | roundInfo[2][1], 1367 | true, 1368 | 'Did not register that the requester successfully paid his fees' 1369 | ) 1370 | 1371 | assert.equal( 1372 | roundInfo[1][2].toNumber(), 1373 | 0, 1374 | 'Should not register any payments for challenger' 1375 | ) 1376 | assert.equal( 1377 | roundInfo[2][2], 1378 | false, 1379 | 'Should not register that challenger successfully paid fees' 1380 | ) 1381 | assert.equal(roundInfo[3].toNumber(), 1800, 'Incorrect FeeRewards value') 1382 | 1383 | const winnerAppealFee = 1384 | arbitrationCost + 1385 | (arbitrationCost * winnerStakeMultiplier) / MULTIPLIER_DIVISOR // 1200 1386 | 1387 | // Increase time to make sure winner can pay in 2nd half. 1388 | await time.increase(appealTimeOut / 2 + 1) 1389 | 1390 | await proofH.fundAppeal(requester, 0, 2, { 1391 | from: challenger1, 1392 | value: winnerAppealFee 1393 | }) 1394 | 1395 | roundInfo = await proofH.getRoundInfo(requester, 0, 0, 1) 1396 | 1397 | assert.equal( 1398 | roundInfo[1][2].toNumber(), 1399 | 1200, 1400 | 'Registered appeal fee of the challenger is incorrect' 1401 | ) 1402 | assert.equal( 1403 | roundInfo[2][2], 1404 | true, 1405 | 'Should register that the challenger successfully paid his fees' 1406 | ) 1407 | 1408 | assert.equal( 1409 | roundInfo[3].toNumber(), 1410 | 2000, // 1800 + 1200 - 1000 1411 | 'Incorrect FeeRewards value after both sides paid their fees' 1412 | ) 1413 | 1414 | // If both sides pay their fees it starts new appeal round. Check that both sides have their values set to default. 1415 | roundInfo = await proofH.getRoundInfo(requester, 0, 0, 2) 1416 | assert.equal( 1417 | roundInfo[2][1], 1418 | false, 1419 | 'Appeal fee payment for requester should not be registered in the new round' 1420 | ) 1421 | assert.equal( 1422 | roundInfo[2][2], 1423 | false, 1424 | 'Appeal fee payment for challenger should not be registered in the new round' 1425 | ) 1426 | 1427 | // Resolve the first challenge to see if the new challenge will set correct values as well. 1428 | await arbitrator.giveRuling(2, 1) 1429 | await time.increase(appealTimeOut + 1) 1430 | await arbitrator.giveRuling(2, 1) 1431 | 1432 | await proofH.challengeRequest( 1433 | requester, 1434 | 1, 1435 | '0x0000000000000000000000000000000000000000', 1436 | '', 1437 | { from: challenger2, value: arbitrationCost } 1438 | ) 1439 | await arbitrator.giveRuling(3, 0) // Give 0 ruling to check shared multiplier this time. 1440 | 1441 | await proofH.fundAppeal(requester, 1, 1, { from: requester, value: 1e18 }) 1442 | 1443 | roundInfo = await proofH.getRoundInfo(requester, 0, 1, 1) 1444 | 1445 | assert.equal( 1446 | roundInfo[1][1].toNumber(), 1447 | 1500, // With shared multiplier = 5000 the sharedFee is 1500 1448 | 'Registered fee of the requester is incorrect' 1449 | ) 1450 | assert.equal( 1451 | roundInfo[2][1], 1452 | true, 1453 | 'Did not register that the requester successfully paid his fees' 1454 | ) 1455 | assert.equal(roundInfo[3].toNumber(), 1500, 'Incorrect FeeRewards value') 1456 | 1457 | await proofH.fundAppeal(requester, 1, 2, { 1458 | from: challenger1, 1459 | value: 1500 1460 | }) 1461 | 1462 | roundInfo = await proofH.getRoundInfo(requester, 0, 1, 1) 1463 | 1464 | assert.equal( 1465 | roundInfo[1][2].toNumber(), 1466 | 1500, 1467 | 'Registered appeal fee of the challenger is incorrect' 1468 | ) 1469 | assert.equal( 1470 | roundInfo[2][2], 1471 | true, 1472 | 'Should register that the challenger successfully paid his fees' 1473 | ) 1474 | 1475 | assert.equal( 1476 | roundInfo[3].toNumber(), 1477 | 2000, 1478 | 'Incorrect FeeRewards value after both sides paid their fees' 1479 | ) 1480 | 1481 | roundInfo = await proofH.getRoundInfo(requester, 0, 1, 2) 1482 | assert.equal( 1483 | roundInfo[2][1], 1484 | false, 1485 | 'Appeal fee payment for requester should not be registered in the new round' 1486 | ) 1487 | assert.equal( 1488 | roundInfo[2][2], 1489 | false, 1490 | 'Appeal fee payment for challenger should not be registered in the new round' 1491 | ) 1492 | }) 1493 | 1494 | it('Should not be possible to fund appeal if the timeout has passed', async () => { 1495 | await proofH.addSubmission('', { 1496 | from: requester, 1497 | value: requesterTotalCost 1498 | }) 1499 | await proofH.addVouch(requester, { from: voucher1 }) 1500 | await proofH.addVouch(requester, { from: voucher2 }) 1501 | 1502 | await proofH.changeStateToPending(requester, [voucher1, voucher2], { 1503 | from: governor 1504 | }) 1505 | await proofH.challengeRequest( 1506 | requester, 1507 | 2, 1508 | '0x0000000000000000000000000000000000000000', 1509 | '', 1510 | { from: challenger1, value: arbitrationCost } 1511 | ) 1512 | await arbitrator.giveRuling(1, 1) 1513 | 1514 | const loserAppealFee = 1515 | arbitrationCost + 1516 | (arbitrationCost * winnerStakeMultiplier) / MULTIPLIER_DIVISOR 1517 | 1518 | await time.increase(appealTimeOut / 2 + 1) 1519 | await expectRevert( 1520 | proofH.fundAppeal(requester, 0, 2, { 1521 | from: challenger1, 1522 | value: loserAppealFee 1523 | }), 1524 | 'Appeal period is over for loser' 1525 | ) 1526 | const winnerAppealFee = 1527 | arbitrationCost + 1528 | (arbitrationCost * winnerStakeMultiplier) / MULTIPLIER_DIVISOR 1529 | 1530 | await time.increase(appealTimeOut / 2 + 1) 1531 | await expectRevert( 1532 | proofH.fundAppeal(requester, 0, 1, { 1533 | from: requester, 1534 | value: winnerAppealFee 1535 | }), 1536 | 'Appeal period is over' 1537 | ) 1538 | }) 1539 | 1540 | it('Should correctly reset the challenge period if the requester wins', async () => { 1541 | await proofH.addSubmission('', { 1542 | from: requester, 1543 | value: requesterTotalCost 1544 | }) 1545 | await proofH.addVouch(requester, { from: voucher1 }) 1546 | await proofH.addVouch(requester, { from: voucher2 }) 1547 | 1548 | await proofH.changeStateToPending(requester, [voucher1, voucher2], { 1549 | from: governor 1550 | }) 1551 | 1552 | await proofH.challengeRequest( 1553 | requester, 1554 | 2, 1555 | '0x0000000000000000000000000000000000000000', 1556 | '', 1557 | { from: challenger1, value: arbitrationCost } 1558 | ) 1559 | 1560 | await arbitrator.giveRuling(1, 1) 1561 | await time.increase(appealTimeOut + 1) 1562 | await arbitrator.giveRuling(1, 1) 1563 | 1564 | let request = await proofH.getRequestInfo(requester, 0) 1565 | assert.equal(request[0], false, 'The request should not be disputed') 1566 | assert.equal( 1567 | request[3].toNumber(), 1568 | 0, 1569 | 'Current reason should be reset to 0' 1570 | ) 1571 | 1572 | // Check that it's not possible to challenge with the same reason. 1573 | await expectRevert( 1574 | proofH.challengeRequest( 1575 | requester, 1576 | 2, 1577 | '0x0000000000000000000000000000000000000000', 1578 | '', 1579 | { from: challenger1, value: arbitrationCost } 1580 | ), 1581 | 'The reason has already been used' 1582 | ) 1583 | 1584 | // Also check that the execution of the request is still possible if there is no dispute. 1585 | await time.increase(challengePeriodDuration + 1) 1586 | const oldBalance = await web3.eth.getBalance(requester) 1587 | await proofH.executeRequest(requester, { from: governor }) 1588 | const newBalance = await web3.eth.getBalance(requester) 1589 | assert( 1590 | new BN(newBalance).eq(new BN(oldBalance).add(new BN(requesterTotalCost))), 1591 | 'The requester was not reimbursed correctly' 1592 | ) 1593 | 1594 | const submission = await proofH.getSubmissionInfo(requester) 1595 | assert.equal( 1596 | submission[0].toNumber(), 1597 | 0, 1598 | 'The submission should have a default status' 1599 | ) 1600 | assert.equal(submission[4], true, 'The submission should be registered') 1601 | request = await proofH.getRequestInfo(requester, 0) 1602 | assert.equal(request[1], true, 'The request should be resolved') 1603 | }) 1604 | 1605 | it('Should register the submission if the requester won in all 4 reasons', async () => { 1606 | await proofH.addSubmission('', { 1607 | from: requester, 1608 | value: requesterTotalCost 1609 | }) 1610 | await proofH.addVouch(requester, { from: voucher1 }) 1611 | await proofH.addVouch(requester, { from: voucher2 }) 1612 | 1613 | await proofH.changeStateToPending(requester, [voucher1, voucher2], { 1614 | from: governor 1615 | }) 1616 | 1617 | await proofH.challengeRequest( 1618 | requester, 1619 | 2, 1620 | '0x0000000000000000000000000000000000000000', 1621 | '', 1622 | { from: challenger1, value: arbitrationCost } 1623 | ) 1624 | await arbitrator.giveRuling(1, 1) 1625 | await time.increase(appealTimeOut + 1) 1626 | await arbitrator.giveRuling(1, 1) 1627 | 1628 | await proofH.challengeRequest( 1629 | requester, 1630 | 1, 1631 | '0x0000000000000000000000000000000000000000', 1632 | '', 1633 | { from: challenger1, value: arbitrationCost } 1634 | ) 1635 | await arbitrator.giveRuling(2, 1) 1636 | await time.increase(appealTimeOut + 1) 1637 | await arbitrator.giveRuling(2, 1) 1638 | 1639 | // Make a parallel request to see if it's handled correctly. 1640 | await proofH.challengeRequest(requester, 3, voucher1, '', { 1641 | from: challenger1, 1642 | value: arbitrationCost 1643 | }) 1644 | await proofH.challengeRequest(requester, 3, voucher2, '', { 1645 | from: challenger2, 1646 | value: arbitrationCost 1647 | }) 1648 | await arbitrator.giveRuling(3, 1) 1649 | await arbitrator.giveRuling(4, 1) 1650 | await time.increase(appealTimeOut + 1) 1651 | await arbitrator.giveRuling(3, 1) 1652 | await arbitrator.giveRuling(4, 1) 1653 | 1654 | // Check that the info stored in the request is correct so far. 1655 | let submission = await proofH.getSubmissionInfo(requester) 1656 | assert.equal( 1657 | submission[4], 1658 | false, 1659 | 'The submission should not be registered yet' 1660 | ) 1661 | 1662 | let request = await proofH.getRequestInfo(requester, 0) 1663 | assert.equal(request[1], false, 'The request should not be resolved yet') 1664 | assert.equal(request[5].toNumber(), 5, 'Incorrect number of challenges') 1665 | assert.equal(request[9].toNumber(), 7, 'Incorrect reasons bitmap') 1666 | // Check the data of a random challenge as well. 1667 | const challengeInfo = await proofH.getChallengeInfo(requester, 0, 3) 1668 | assert.equal( 1669 | challengeInfo[2].toNumber(), 1670 | 4, 1671 | 'Challenge ID does not correspond with a dispute ID' 1672 | ) 1673 | assert.equal( 1674 | challengeInfo[3].toNumber(), 1675 | 1, 1676 | 'Incorrect ruling of the challenge' 1677 | ) 1678 | 1679 | await proofH.challengeRequest( 1680 | requester, 1681 | 4, 1682 | '0x0000000000000000000000000000000000000000', 1683 | '', 1684 | { from: challenger2, value: arbitrationCost } 1685 | ) 1686 | await arbitrator.giveRuling(5, 1) 1687 | await time.increase(appealTimeOut + 1) 1688 | await arbitrator.giveRuling(5, 1) 1689 | 1690 | request = await proofH.getRequestInfo(requester, 0) 1691 | assert.equal(request[1], true, 'The request should be resolved') 1692 | assert.equal( 1693 | request[4].toNumber(), 1694 | 0, 1695 | 'Should not be any parallel disputes' 1696 | ) 1697 | assert.equal( 1698 | request[9].toNumber(), 1699 | 15, 1700 | 'Incorrect reasons bitmap in the end' 1701 | ) 1702 | 1703 | submission = await proofH.getSubmissionInfo(requester) 1704 | assert.equal( 1705 | submission[0].toNumber(), 1706 | 0, 1707 | 'The submission should have a default status' 1708 | ) 1709 | assert.equal(submission[4], true, 'The submission should be registered') 1710 | }) 1711 | 1712 | it('Should set correct values if arbitrator refuses to rule', async () => { 1713 | await proofH.addSubmission('', { 1714 | from: requester, 1715 | value: requesterTotalCost 1716 | }) 1717 | await proofH.addVouch(requester, { from: voucher1 }) 1718 | await proofH.addVouch(requester, { from: voucher2 }) 1719 | 1720 | await proofH.changeStateToPending(requester, [voucher1, voucher2], { 1721 | from: governor 1722 | }) 1723 | 1724 | // Make a parallel request to see if it's handled correctly. 1725 | await proofH.challengeRequest(requester, 3, voucher1, '', { 1726 | from: challenger1, 1727 | value: arbitrationCost 1728 | }) 1729 | await proofH.challengeRequest(requester, 3, voucher2, '', { 1730 | from: challenger2, 1731 | value: arbitrationCost 1732 | }) 1733 | await arbitrator.giveRuling(1, 0) 1734 | await arbitrator.giveRuling(2, 1) 1735 | await time.increase(appealTimeOut + 1) 1736 | await arbitrator.giveRuling(1, 0) 1737 | await arbitrator.giveRuling(2, 1) 1738 | 1739 | // The requester didn't win the first dispute so his request should be declined in the end. 1740 | const submission = await proofH.getSubmissionInfo(requester) 1741 | assert.equal( 1742 | submission[0].toNumber(), 1743 | 0, 1744 | 'The submission should have a default status' 1745 | ) 1746 | assert.equal( 1747 | submission[4], 1748 | false, 1749 | 'The submission should not be registered' 1750 | ) 1751 | 1752 | const request = await proofH.getRequestInfo(requester, 0) 1753 | assert.equal(request[1], true, 'The request should be resolved') 1754 | assert.equal(request[2], true, 'requsterLost should be marked as true') 1755 | assert.equal( 1756 | request[8], 1757 | '0x0000000000000000000000000000000000000000', 1758 | 'Ultimate challenger should not be defined if 0 ruling wins' 1759 | ) 1760 | 1761 | const challengeInfo1 = await proofH.getChallengeInfo(requester, 0, 0) 1762 | assert.equal( 1763 | challengeInfo1[3].toNumber(), 1764 | 0, 1765 | 'Incorrect ruling of the first challenge' 1766 | ) 1767 | const challengeInfo2 = await proofH.getChallengeInfo(requester, 0, 1) 1768 | assert.equal( 1769 | challengeInfo2[3].toNumber(), 1770 | 1, 1771 | 'Incorrect ruling of the second challenge' 1772 | ) 1773 | }) 1774 | 1775 | it('Should set correct values if challenger wins', async () => { 1776 | await proofH.addSubmission('', { 1777 | from: requester, 1778 | value: requesterTotalCost 1779 | }) 1780 | await proofH.addVouch(requester, { from: voucher1 }) 1781 | await proofH.addVouch(requester, { from: voucher2 }) 1782 | 1783 | await proofH.changeStateToPending(requester, [voucher1, voucher2], { 1784 | from: governor 1785 | }) 1786 | 1787 | await proofH.challengeRequest( 1788 | requester, 1789 | 1, 1790 | '0x0000000000000000000000000000000000000000', 1791 | '', 1792 | { from: challenger1, value: arbitrationCost } 1793 | ) 1794 | await arbitrator.giveRuling(1, 2) 1795 | await time.increase(appealTimeOut + 1) 1796 | await arbitrator.giveRuling(1, 2) 1797 | 1798 | const submission = await proofH.getSubmissionInfo(requester) 1799 | assert.equal( 1800 | submission[0].toNumber(), 1801 | 0, 1802 | 'The submission should have a default status' 1803 | ) 1804 | assert.equal( 1805 | submission[4], 1806 | false, 1807 | 'The submission should not be registered' 1808 | ) 1809 | 1810 | const request = await proofH.getRequestInfo(requester, 0) 1811 | assert.equal(request[1], true, 'The request should be resolved') 1812 | assert.equal(request[2], true, 'requsterLost should be marked as true') 1813 | assert.equal(request[8], challenger1, 'Incorrect ultimate challenger') 1814 | 1815 | const challengeInfo = await proofH.getChallengeInfo(requester, 0, 0) 1816 | assert.equal(challengeInfo[3].toNumber(), 2, 'Incorrect ruling') 1817 | }) 1818 | 1819 | it('Should switch the winning challenger in reason Duplicate', async () => { 1820 | await proofH.addSubmission('', { 1821 | from: requester, 1822 | value: requesterTotalCost 1823 | }) 1824 | await proofH.addVouch(requester, { from: voucher1 }) 1825 | await proofH.addVouch(requester, { from: voucher2 }) 1826 | 1827 | await proofH.changeStateToPending(requester, [voucher1, voucher2], { 1828 | from: governor 1829 | }) 1830 | 1831 | // Voucher1 is the earliest submission so challenger2 should be the ultimate challenger in the end. 1832 | await proofH.challengeRequest(requester, 3, voucher3, '', { 1833 | from: challenger1, 1834 | value: arbitrationCost 1835 | }) 1836 | await proofH.challengeRequest(requester, 3, voucher1, '', { 1837 | from: challenger2, 1838 | value: arbitrationCost 1839 | }) 1840 | await proofH.challengeRequest(requester, 3, voucher2, '', { 1841 | from: other, 1842 | value: arbitrationCost 1843 | }) 1844 | await arbitrator.giveRuling(1, 2) 1845 | await arbitrator.giveRuling(2, 2) 1846 | await arbitrator.giveRuling(3, 2) 1847 | await time.increase(appealTimeOut + 1) 1848 | 1849 | await arbitrator.giveRuling(1, 2) 1850 | let request = await proofH.getRequestInfo(requester, 0) 1851 | assert.equal(request[1], false, 'The request should not be resolved yet') 1852 | assert.equal( 1853 | request[8], 1854 | challenger1, 1855 | 'Incorrect ultimate challenger after the 1st ruling' 1856 | ) 1857 | 1858 | await arbitrator.giveRuling(2, 2) 1859 | request = await proofH.getRequestInfo(requester, 0) 1860 | assert.equal( 1861 | request[8], 1862 | challenger2, 1863 | 'Ultimate challenger should be switched after the 2nd ruling' 1864 | ) 1865 | 1866 | await arbitrator.giveRuling(3, 2) 1867 | request = await proofH.getRequestInfo(requester, 0) 1868 | assert.equal(request[1], true, 'The request should be resolved') 1869 | assert.equal( 1870 | request[8], 1871 | challenger2, 1872 | 'Ultimate challenger should stay the same after the 3rd ruling' 1873 | ) 1874 | }) 1875 | 1876 | it('Should set correct values if requester wins removal request', async () => { 1877 | await proofH.removeSubmission(voucher1, '', { 1878 | from: requester, 1879 | value: requesterTotalCost 1880 | }) 1881 | await proofH.challengeRequest( 1882 | voucher1, 1883 | 0, 1884 | '0x0000000000000000000000000000000000000000', 1885 | '', 1886 | { from: challenger1, value: arbitrationCost } 1887 | ) 1888 | await arbitrator.giveRuling(1, 1) 1889 | await time.increase(appealTimeOut + 1) 1890 | await arbitrator.giveRuling(1, 1) 1891 | 1892 | const submission = await proofH.getSubmissionInfo(voucher1) 1893 | assert.equal( 1894 | submission[0].toNumber(), 1895 | 0, 1896 | 'The submission should have a default status' 1897 | ) 1898 | assert.equal( 1899 | submission[4], 1900 | false, 1901 | 'The submission should not be registered' 1902 | ) 1903 | 1904 | const request = await proofH.getRequestInfo(voucher1, 1) 1905 | assert.equal(request[1], true, 'The request should be resolved') 1906 | 1907 | const challengeInfo = await proofH.getChallengeInfo(voucher1, 1, 0) 1908 | assert.equal(challengeInfo[3].toNumber(), 1, 'Incorrect ruling') 1909 | }) 1910 | 1911 | it('Should set correct values if challenger wins removal request', async () => { 1912 | await proofH.removeSubmission(voucher1, '', { 1913 | from: requester, 1914 | value: requesterTotalCost 1915 | }) 1916 | await proofH.challengeRequest( 1917 | voucher1, 1918 | 0, 1919 | '0x0000000000000000000000000000000000000000', 1920 | '', 1921 | { from: challenger1, value: arbitrationCost } 1922 | ) 1923 | await arbitrator.giveRuling(1, 2) 1924 | await time.increase(appealTimeOut + 1) 1925 | await arbitrator.giveRuling(1, 2) 1926 | 1927 | const submission = await proofH.getSubmissionInfo(voucher1) 1928 | assert.equal( 1929 | submission[0].toNumber(), 1930 | 0, 1931 | 'The submission should have a default status' 1932 | ) 1933 | assert.equal( 1934 | submission[4], 1935 | true, 1936 | 'The submission should still be registered' 1937 | ) 1938 | 1939 | const request = await proofH.getRequestInfo(voucher1, 1) 1940 | assert.equal(request[1], true, 'The request should be resolved') 1941 | 1942 | const challengeInfo = await proofH.getChallengeInfo(voucher1, 1, 0) 1943 | assert.equal(challengeInfo[3].toNumber(), 2, 'Incorrect ruling') 1944 | }) 1945 | 1946 | it('Should change the ruling if the loser paid appeal fee while winner did not', async () => { 1947 | await proofH.addSubmission('', { 1948 | from: requester, 1949 | value: requesterTotalCost 1950 | }) 1951 | await proofH.addVouch(requester, { from: voucher1 }) 1952 | await proofH.addVouch(requester, { from: voucher2 }) 1953 | 1954 | await proofH.changeStateToPending(requester, [voucher1, voucher2], { 1955 | from: governor 1956 | }) 1957 | await proofH.challengeRequest( 1958 | requester, 1959 | 2, 1960 | '0x0000000000000000000000000000000000000000', 1961 | '', 1962 | { from: challenger1, value: arbitrationCost } 1963 | ) 1964 | await arbitrator.giveRuling(1, 1) 1965 | 1966 | await proofH.fundAppeal(requester, 0, 2, { from: challenger1, value: 1e18 }) 1967 | 1968 | await time.increase(appealTimeOut + 1) 1969 | await arbitrator.giveRuling(1, 1) 1970 | 1971 | const request = await proofH.getRequestInfo(requester, 0) 1972 | assert.equal(request[8], challenger1, 'Incorrect ultimate challenger') 1973 | const challengeInfo = await proofH.getChallengeInfo(requester, 0, 0) 1974 | assert.equal( 1975 | challengeInfo[3].toNumber(), 1976 | 2, 1977 | 'The ruling should be switched to challenger' 1978 | ) 1979 | }) 1980 | 1981 | it('Should process vouches correctly', async () => { 1982 | await proofH.addSubmission('', { 1983 | from: requester, 1984 | value: requesterTotalCost 1985 | }) 1986 | await proofH.addVouch(requester, { from: voucher1 }) 1987 | await proofH.addVouch(requester, { from: voucher2 }) 1988 | 1989 | await proofH.changeStateToPending(requester, [voucher1, voucher2], { 1990 | from: governor 1991 | }) 1992 | 1993 | await proofH.challengeRequest( 1994 | requester, 1995 | 1, 1996 | '0x0000000000000000000000000000000000000000', 1997 | '', 1998 | { from: challenger1, value: arbitrationCost } 1999 | ) 2000 | await arbitrator.giveRuling(1, 2) 2001 | await time.increase(appealTimeOut + 1) 2002 | await expectRevert( 2003 | proofH.processVouches(requester, 0, 1, { from: governor }), 2004 | 'Submission must be resolved' 2005 | ) 2006 | // Let challenger win to make the test more transparent. 2007 | await arbitrator.giveRuling(1, 2) 2008 | 2009 | await proofH.processVouches(requester, 0, 1, { from: governor }) 2010 | const voucher1Info = await proofH.getSubmissionInfo(voucher1) 2011 | assert.equal( 2012 | voucher1Info[5], 2013 | false, 2014 | 'First voucher should not be marked as used' 2015 | ) 2016 | let voucher2Info = await proofH.getSubmissionInfo(voucher2) 2017 | assert.equal( 2018 | voucher2Info[5], 2019 | true, 2020 | 'Second voucher should still be marked as used' 2021 | ) 2022 | const submission1 = await proofH.getSubmissionInfo(voucher1) 2023 | assert.equal( 2024 | submission1[4], 2025 | true, 2026 | 'The first submission should still be registered' 2027 | ) 2028 | 2029 | await proofH.processVouches(requester, 0, 1, { from: governor }) 2030 | voucher2Info = await proofH.getSubmissionInfo(voucher2) 2031 | assert.equal( 2032 | voucher2Info[5], 2033 | false, 2034 | 'Second voucher should not be marked as used' 2035 | ) 2036 | const submission2 = await proofH.getSubmissionInfo(voucher2) 2037 | assert.equal( 2038 | submission2[4], 2039 | true, 2040 | 'The second submission should still be registered' 2041 | ) 2042 | }) 2043 | 2044 | it('Should correctly penalize vouchers that vote for a bad submission', async () => { 2045 | // Make it so one of the vouchers is in the middle of reapplication process. 2046 | await time.increase(submissionDuration - renewalPeriodDuration) 2047 | 2048 | await proofH.addSubmission('', { 2049 | from: requester, 2050 | value: requesterTotalCost 2051 | }) 2052 | await proofH.addVouch(requester, { from: voucher1 }) 2053 | await proofH.addVouch(requester, { from: voucher2 }) 2054 | 2055 | await proofH.changeStateToPending(requester, [voucher1, voucher2], { 2056 | from: governor 2057 | }) 2058 | 2059 | await proofH.challengeRequest( 2060 | requester, 2061 | 4, 2062 | '0x0000000000000000000000000000000000000000', 2063 | '', 2064 | { from: challenger1, value: arbitrationCost } 2065 | ) 2066 | 2067 | // Change required number of vouches to 1 because the rest 2 are used. 2068 | await proofH.changeRequiredNumberOfVouches(1, { from: governor }) 2069 | await proofH.reapplySubmission('', { 2070 | from: voucher1, 2071 | value: requesterTotalCost 2072 | }) 2073 | await proofH.addVouch(voucher1, { from: voucher3 }) 2074 | await proofH.changeStateToPending(voucher1, [voucher3], { from: governor }) 2075 | 2076 | await arbitrator.giveRuling(1, 2) 2077 | await time.increase(appealTimeOut + 1) 2078 | await arbitrator.giveRuling(1, 2) 2079 | 2080 | await proofH.processVouches(requester, 0, 2, { from: governor }) 2081 | let submission1 = await proofH.getSubmissionInfo(voucher1) 2082 | assert.equal( 2083 | submission1[4], 2084 | false, 2085 | 'The first submission should not be registered' 2086 | ) 2087 | const submission2 = await proofH.getSubmissionInfo(voucher2) 2088 | assert.equal( 2089 | submission2[4], 2090 | false, 2091 | 'The second submission should not be registered' 2092 | ) 2093 | 2094 | let request = await proofH.getRequestInfo(voucher1, 1) 2095 | assert.equal(request[2], true, 'requsterLost should be marked as true') 2096 | await time.increase(challengePeriodDuration + 1) 2097 | await proofH.executeRequest(voucher1, { from: governor }) 2098 | 2099 | submission1 = await proofH.getSubmissionInfo(voucher1) 2100 | assert.equal( 2101 | submission1[0].toNumber(), 2102 | 0, 2103 | 'The first submission should have a default status' 2104 | ) 2105 | assert.equal( 2106 | submission1[4], 2107 | false, 2108 | 'The first submission still should not be registered' 2109 | ) 2110 | request = await proofH.getRequestInfo(voucher1, 1) 2111 | assert.equal(request[1], true, 'Reapplication request should be resolved') 2112 | }) 2113 | 2114 | it('Ultimate challenger should take feeRewards of the first challenge', async () => { 2115 | await proofH.addSubmission('', { 2116 | from: requester, 2117 | value: requesterTotalCost 2118 | }) 2119 | await proofH.addVouch(requester, { from: voucher1 }) 2120 | await proofH.addVouch(requester, { from: voucher2 }) 2121 | 2122 | await proofH.changeStateToPending(requester, [voucher1, voucher2], { 2123 | from: governor 2124 | }) 2125 | 2126 | await proofH.challengeRequest(requester, 3, voucher1, '', { 2127 | from: challenger1, 2128 | value: arbitrationCost 2129 | }) 2130 | await proofH.challengeRequest(requester, 3, voucher2, '', { 2131 | from: challenger2, 2132 | value: arbitrationCost 2133 | }) 2134 | 2135 | await arbitrator.giveRuling(1, 1) 2136 | await arbitrator.giveRuling(2, 2) 2137 | await time.increase(appealTimeOut + 1) 2138 | await arbitrator.giveRuling(1, 1) 2139 | 2140 | await expectRevert( 2141 | proofH.withdrawFeesAndRewards(challenger2, requester, 0, 0, 0, { 2142 | from: governor 2143 | }), 2144 | 'Submission must be resolved' 2145 | ) 2146 | await arbitrator.giveRuling(2, 2) 2147 | 2148 | await expectRevert( 2149 | proofH.withdrawFeesAndRewards( 2150 | '0x0000000000000000000000000000000000000000', 2151 | requester, 2152 | 0, 2153 | 0, 2154 | 0, 2155 | { from: governor } 2156 | ), 2157 | 'Beneficiary must not be empty' 2158 | ) 2159 | const oldBalanceRequester = await web3.eth.getBalance(requester) 2160 | await proofH.withdrawFeesAndRewards(requester, requester, 0, 0, 0, { 2161 | from: governor 2162 | }) 2163 | const newBalanceRequester = await web3.eth.getBalance(requester) 2164 | // Requester's fee of the first dispute should go to the ultimate challenger. 2165 | assert( 2166 | new BN(newBalanceRequester).eq(new BN(oldBalanceRequester)), 2167 | 'The balance of the requester should stay the same' 2168 | ) 2169 | 2170 | // Only check the 2nd challenger, because the 1st challenger didn't win a dispute. 2171 | let oldBalanceChallenger = await web3.eth.getBalance(challenger2) 2172 | await proofH.withdrawFeesAndRewards(challenger2, requester, 0, 0, 0, { 2173 | from: governor 2174 | }) 2175 | let newBalanceChallenger = await web3.eth.getBalance(challenger2) 2176 | assert( 2177 | new BN(newBalanceChallenger).eq( 2178 | new BN(oldBalanceChallenger).add(new BN(requesterTotalCost)) 2179 | ), 2180 | 'The challenger has incorrect balance after withdrawing from 0 challenge' 2181 | ) 2182 | oldBalanceChallenger = await web3.eth.getBalance(challenger2) 2183 | await proofH.withdrawFeesAndRewards(challenger2, requester, 0, 1, 0, { 2184 | from: governor 2185 | }) 2186 | newBalanceChallenger = await web3.eth.getBalance(challenger2) 2187 | assert( 2188 | new BN(newBalanceChallenger).eq(new BN(oldBalanceChallenger)), 2189 | 'The challenger should have the same balance after withdrawing from 1 challenge' 2190 | ) 2191 | }) 2192 | 2193 | it('Should not withdraw anything from the subsequent challenge', async () => { 2194 | await proofH.addSubmission('', { 2195 | from: requester, 2196 | value: requesterTotalCost * 0.2 2197 | }) 2198 | await proofH.addVouch(requester, { from: voucher1 }) 2199 | await proofH.addVouch(requester, { from: voucher2 }) 2200 | await proofH.fundSubmission(requester, { from: other, value: 1e18 }) 2201 | 2202 | await proofH.changeStateToPending(requester, [voucher1, voucher2], { 2203 | from: governor 2204 | }) 2205 | 2206 | await proofH.challengeRequest(requester, 3, voucher1, '', { 2207 | from: challenger1, 2208 | value: arbitrationCost 2209 | }) 2210 | await proofH.challengeRequest(requester, 3, voucher2, '', { 2211 | from: challenger2, 2212 | value: arbitrationCost 2213 | }) 2214 | 2215 | await arbitrator.giveRuling(1, 1) 2216 | await arbitrator.giveRuling(2, 1) 2217 | await time.increase(appealTimeOut + 1) 2218 | await arbitrator.giveRuling(1, 1) 2219 | await arbitrator.giveRuling(2, 1) 2220 | 2221 | await time.increase(challengePeriodDuration + 1) 2222 | let oldBalanceRequester = await web3.eth.getBalance(requester) 2223 | await proofH.executeRequest(requester, { from: governor }) 2224 | let newBalanceRequester = await web3.eth.getBalance(requester) 2225 | assert( 2226 | new BN(newBalanceRequester).eq( 2227 | new BN(oldBalanceRequester).add(new BN(1200)) // The requester only did a partial funding so he should be reimbursed according to that (0.2 * feeRewards). 2228 | ), 2229 | 'The balance of the requester is incorrect after withdrawing from 0 challenge' 2230 | ) 2231 | const oldBalanceCrowdfunder = await web3.eth.getBalance(other) 2232 | await proofH.withdrawFeesAndRewards(other, requester, 0, 0, 0, { 2233 | from: governor 2234 | }) 2235 | const newBalanceCrowdfunder = await web3.eth.getBalance(other) 2236 | assert( 2237 | new BN(newBalanceCrowdfunder).eq( 2238 | new BN(oldBalanceCrowdfunder).add(new BN(4800)) // 0.8 * feeRewards. 2239 | ), 2240 | 'The balance of the crowdfunder is incorrect' 2241 | ) 2242 | 2243 | oldBalanceRequester = await web3.eth.getBalance(requester) 2244 | await proofH.withdrawFeesAndRewards(requester, requester, 0, 1, 0, { 2245 | from: governor 2246 | }) 2247 | newBalanceRequester = await web3.eth.getBalance(requester) 2248 | assert( 2249 | new BN(newBalanceRequester).eq(new BN(oldBalanceRequester)), 2250 | 'The balance of the requester should stay the same' 2251 | ) 2252 | }) 2253 | 2254 | it('Should withdraw fees correctly if arbitrator refused to rule', async () => { 2255 | await proofH.addSubmission('', { 2256 | from: requester, 2257 | value: requesterTotalCost 2258 | }) 2259 | await proofH.addVouch(requester, { from: voucher1 }) 2260 | await proofH.addVouch(requester, { from: voucher2 }) 2261 | 2262 | await proofH.changeStateToPending(requester, [voucher1, voucher2], { 2263 | from: governor 2264 | }) 2265 | 2266 | await proofH.challengeRequest(requester, 3, voucher1, '', { 2267 | from: challenger1, 2268 | value: arbitrationCost 2269 | }) 2270 | await proofH.challengeRequest(requester, 3, voucher2, '', { 2271 | from: challenger2, 2272 | value: arbitrationCost 2273 | }) 2274 | 2275 | await arbitrator.giveRuling(1, 0) 2276 | await arbitrator.giveRuling(2, 1) 2277 | await time.increase(appealTimeOut + 1) 2278 | await arbitrator.giveRuling(1, 0) 2279 | await arbitrator.giveRuling(2, 1) 2280 | 2281 | let oldBalanceRequester = await web3.eth.getBalance(requester) 2282 | await proofH.withdrawFeesAndRewards(requester, requester, 0, 0, 0, { 2283 | from: governor 2284 | }) 2285 | let newBalanceRequester = await web3.eth.getBalance(requester) 2286 | assert( 2287 | new BN(newBalanceRequester).eq( 2288 | new BN(oldBalanceRequester).add(new BN(5142)) // 6000/7000 * 6000 = 5142.8 2289 | ), 2290 | 'The balance of the requester is incorrect after withdrawing from 0 challenge' 2291 | ) 2292 | // Only check the 1st challenger, because the 2nd challenger lost a dispute. 2293 | const oldBalanceChallenger = await web3.eth.getBalance(challenger1) 2294 | await proofH.withdrawFeesAndRewards(challenger1, requester, 0, 0, 0, { 2295 | from: governor 2296 | }) 2297 | const newBalanceChallenger = await web3.eth.getBalance(challenger1) 2298 | assert( 2299 | new BN(newBalanceChallenger).eq( 2300 | new BN(oldBalanceChallenger).add(new BN(857)) // 1000/7000 * 6000 = 857.1 2301 | ), 2302 | 'The balance of the challenger is incorrect after withdrawing from 0 challenge' 2303 | ) 2304 | 2305 | oldBalanceRequester = await web3.eth.getBalance(requester) 2306 | await proofH.withdrawFeesAndRewards(requester, requester, 0, 1, 0, { 2307 | from: governor 2308 | }) 2309 | newBalanceRequester = await web3.eth.getBalance(requester) 2310 | 2311 | assert( 2312 | new BN(newBalanceRequester).eq(new BN(oldBalanceRequester)), 2313 | 'The requester should not get any reward from 1 challenge' 2314 | ) 2315 | }) 2316 | 2317 | it('Should make governance changes', async () => { 2318 | await expectRevert( 2319 | proofH.addSubmissionManually(other, '', { from: other }), 2320 | 'The caller must be the governor.' 2321 | ) 2322 | await expectRevert( 2323 | proofH.removeSubmissionManually(voucher1, { from: other }), 2324 | 'The caller must be the governor.' 2325 | ) 2326 | // submissionBaseDeposit 2327 | await expectRevert( 2328 | proofH.changeSubmissionBaseDeposit(22, { from: other }), 2329 | 'The caller must be the governor.' 2330 | ) 2331 | await proofH.changeSubmissionBaseDeposit(22, { from: governor }) 2332 | assert.equal( 2333 | (await proofH.submissionBaseDeposit()).toNumber(), 2334 | 22, 2335 | 'Incorrect submissionBaseDeposit value' 2336 | ) 2337 | // submissionDuration 2338 | await expectRevert( 2339 | proofH.changeSubmissionDuration(28, { from: other }), 2340 | 'The caller must be the governor.' 2341 | ) 2342 | await proofH.changeSubmissionDuration(28, { from: governor }) 2343 | assert.equal( 2344 | (await proofH.submissionDuration()).toNumber(), 2345 | 28, 2346 | 'Incorrect submissionDuration value' 2347 | ) 2348 | // renewalPeriodDuration 2349 | await expectRevert( 2350 | proofH.changeRenewalPeriodDuration(94, { from: other }), 2351 | 'The caller must be the governor.' 2352 | ) 2353 | await proofH.changeRenewalPeriodDuration(94, { from: governor }) 2354 | assert.equal( 2355 | (await proofH.renewalPeriodDuration()).toNumber(), 2356 | 94, 2357 | 'Incorrect renewalPeriodDuration value' 2358 | ) 2359 | // challengePeriodDuration 2360 | await expectRevert( 2361 | proofH.changeChallengePeriodDuration(14, { from: other }), 2362 | 'The caller must be the governor.' 2363 | ) 2364 | await proofH.changeChallengePeriodDuration(14, { from: governor }) 2365 | assert.equal( 2366 | (await proofH.challengePeriodDuration()).toNumber(), 2367 | 14, 2368 | 'Incorrect challengePeriodDuration value' 2369 | ) 2370 | // requiredNumberOfVouches 2371 | await expectRevert( 2372 | proofH.changeRequiredNumberOfVouches(1223, { from: other }), 2373 | 'The caller must be the governor.' 2374 | ) 2375 | await proofH.changeRequiredNumberOfVouches(1223, { from: governor }) 2376 | assert.equal( 2377 | (await proofH.requiredNumberOfVouches()).toNumber(), 2378 | 1223, 2379 | 'Incorrect requiredNumberOfVouches value' 2380 | ) 2381 | // sharedStakeMultiplier 2382 | await expectRevert( 2383 | proofH.changeSharedStakeMultiplier(555, { from: other }), 2384 | 'The caller must be the governor.' 2385 | ) 2386 | await proofH.changeSharedStakeMultiplier(555, { from: governor }) 2387 | assert.equal( 2388 | (await proofH.sharedStakeMultiplier()).toNumber(), 2389 | 555, 2390 | 'Incorrect sharedStakeMultiplier value' 2391 | ) 2392 | // winnerStakeMultiplier 2393 | await expectRevert( 2394 | proofH.changeWinnerStakeMultiplier(2001, { from: other }), 2395 | 'The caller must be the governor.' 2396 | ) 2397 | await proofH.changeWinnerStakeMultiplier(2001, { from: governor }) 2398 | assert.equal( 2399 | (await proofH.winnerStakeMultiplier()).toNumber(), 2400 | 2001, 2401 | 'Incorrect winnerStakeMultiplier value' 2402 | ) 2403 | // loserStakeMultiplier 2404 | await expectRevert( 2405 | proofH.changeLoserStakeMultiplier(9555, { from: other }), 2406 | 'The caller must be the governor.' 2407 | ) 2408 | await proofH.changeLoserStakeMultiplier(9555, { from: governor }) 2409 | assert.equal( 2410 | (await proofH.loserStakeMultiplier()).toNumber(), 2411 | 9555, 2412 | 'Incorrect loserStakeMultiplier value' 2413 | ) 2414 | // governor 2415 | await expectRevert( 2416 | proofH.changeGovernor(other, { from: other }), 2417 | 'The caller must be the governor.' 2418 | ) 2419 | await proofH.changeGovernor(other, { from: governor }) 2420 | assert.equal(await proofH.governor(), other, 'Incorrect governor value') 2421 | // metaEvidenceUpdates 2422 | await expectRevert( 2423 | proofH.changeMetaEvidence('1', '2', { from: governor }), 2424 | 'The caller must be the governor.' // Check that the old governor can't change variables anymore. 2425 | ) 2426 | await proofH.changeMetaEvidence('1', '2', { from: other }) 2427 | let arbitratorData = await proofH.arbitratorDataList(1) 2428 | assert.equal( 2429 | arbitratorData[1].toNumber(), 2430 | 1, 2431 | 'Incorrect metaEvidenceUpdates value' 2432 | ) 2433 | assert.equal( 2434 | (await proofH.getArbitratorDataListCount()).toNumber(), 2435 | 2, 2436 | 'Incorrect arbitratorData length' 2437 | ) 2438 | // arbitrator 2439 | await expectRevert( 2440 | proofH.changeArbitrator(governor, '0xff', { from: governor }), 2441 | 'The caller must be the governor.' 2442 | ) 2443 | await proofH.changeArbitrator(governor, '0xff', { from: other }) 2444 | arbitratorData = await proofH.arbitratorDataList(2) 2445 | assert.equal(arbitratorData[0], governor, 'Incorrect arbitrator address') 2446 | assert.equal(arbitratorData[2], '0xff', 'Incorrect extradata') 2447 | assert.equal( 2448 | (await proofH.getArbitratorDataListCount()).toNumber(), 2449 | 3, 2450 | 'Incorrect arbitratorData length' 2451 | ) 2452 | }) 2453 | 2454 | it('Should correctly withdraw the mistakenly added submission', async () => { 2455 | await proofH.addSubmission('evidence1', { 2456 | from: requester, 2457 | value: requesterTotalCost * 0.4 2458 | }) 2459 | 2460 | await proofH.fundSubmission(requester, { from: other, value: 1e18 }) 2461 | 2462 | const oldBalanceRequester = await web3.eth.getBalance(requester) 2463 | const txWithdraw = await proofH.withdrawSubmission({ 2464 | from: requester, 2465 | gasPrice: gasPrice 2466 | }) 2467 | const txFee = txWithdraw.receipt.gasUsed * gasPrice 2468 | 2469 | const newBalanceRequester = await web3.eth.getBalance(requester) 2470 | const submission = await proofH.getSubmissionInfo(requester) 2471 | const request = await proofH.getRequestInfo(requester, 0) 2472 | 2473 | assert( 2474 | new BN(newBalanceRequester).eq( 2475 | new BN(oldBalanceRequester).add( 2476 | new BN(requesterTotalCost * 0.4).sub(new BN(txFee)) 2477 | ) 2478 | ), 2479 | 'The requester has incorrect balance after withdrawal' 2480 | ) 2481 | 2482 | const oldBalanceCrowdfunder = await web3.eth.getBalance(other) 2483 | await proofH.withdrawFeesAndRewards(other, requester, 0, 0, 0, { 2484 | from: governor 2485 | }) 2486 | const newBalanceCrowdfunder = await web3.eth.getBalance(other) 2487 | assert( 2488 | new BN(newBalanceCrowdfunder).eq( 2489 | new BN(oldBalanceCrowdfunder).add(new BN(requesterTotalCost * 0.6)) 2490 | ), 2491 | 'The crowdfunder has incorrect balance after withdrawal' 2492 | ) 2493 | 2494 | assert.equal( 2495 | submission[0].toNumber(), 2496 | 0, 2497 | 'Submission should have a default status' 2498 | ) 2499 | assert.equal(request[1], true, 'The request should be resolved') 2500 | 2501 | await expectRevert( 2502 | proofH.withdrawSubmission({ from: requester }), 2503 | 'Wrong status' 2504 | ) 2505 | }) 2506 | 2507 | it('Submission should not be registered after expiration', async () => { 2508 | await time.increase(submissionDuration + 1) 2509 | const submission = await proofH.getSubmissionInfo(voucher1) 2510 | assert.equal( 2511 | submission[4], 2512 | false, 2513 | 'The submission should not be registered' 2514 | ) 2515 | }) 2516 | }) 2517 | -------------------------------------------------------------------------------- /truffle-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // See 3 | // to customize your Truffle configuration! 4 | compilers: { 5 | solc: { 6 | settings: { 7 | // See the solidity docs for advice about optimization and evmVersion 8 | optimizer: { 9 | enabled: true, 10 | runs: 200 11 | } 12 | }, 13 | version: '0.5.13' // Fetch exact version from solc-bin (default: truffle's version) 14 | } 15 | }, 16 | networks: { 17 | test: { 18 | gas: 8000000, 19 | host: 'localhost', 20 | network_id: '*', 21 | port: 8545 22 | } 23 | } 24 | } 25 | --------------------------------------------------------------------------------