├── .gitattributes ├── .gitignore ├── .husky ├── .gitignore └── pre-commit ├── .lintstagedrc.json ├── .prettierrc.json ├── LICENSE ├── README.md ├── contracts ├── IArbitrable.sol ├── IArbitrator.sol ├── erc-1497 │ └── IEvidence.sol └── examples │ ├── CentralizedArbitratorWithAppeal.sol │ ├── Escrow.sol │ ├── SimpleCentralizedArbitrator.sol │ ├── SimpleEscrow.sol │ └── SimpleEscrowWithERC1497.sol ├── docs ├── Makefile ├── a-simple-dapp.rst ├── arbitrable.rst ├── arbitrator.rst ├── conf.py ├── erc-1497.rst ├── erc1497.png ├── erc792.png ├── implementing-a-complex-arbitrable.rst ├── implementing-a-complex-arbitrator.rst ├── implementing-an-arbitrable.rst ├── implementing-an-arbitrator.rst ├── index.rst ├── introduction.rst ├── make.bat ├── requirements.txt └── wrap-up.rst ├── hardhat.config.js ├── package.json ├── public ├── favicon.ico ├── index.html └── manifest.json ├── src ├── app.js ├── deploy.js ├── ethereum │ ├── arbitrator.js │ ├── arbitrator.json │ ├── generate-evidence.js │ ├── generate-meta-evidence.js │ ├── simple-escrow-with-erc1497.js │ ├── simple-escrow-with-erc1497.json │ └── web3.js ├── index.css ├── index.js ├── interact.js └── ipfs-publish.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 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # Dependencies 4 | /node_modules/ 5 | 6 | # Production 7 | /build/ 8 | 9 | # Docs 10 | /docs/livehtml/ 11 | /docs/build/ 12 | 13 | # Logs 14 | /yarn-debug.log* 15 | /yarn-error.log* 16 | 17 | # Editors 18 | /.vscode/ 19 | /.idea/* 20 | 21 | # Misc 22 | /.DS_Store 23 | 24 | node_modules 25 | .env 26 | coverage 27 | coverage.json 28 | typechain 29 | 30 | #Hardhat files 31 | cache 32 | artifacts 33 | -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn lint-staged 5 | -------------------------------------------------------------------------------- /.lintstagedrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "*.{json,md,sol}": "prettier --write" 3 | } 4 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "htmlWhitespaceSensitivity": "css", 3 | "printWidth": 120, 4 | "trailingComma": "es5", 5 | "overrides": [ 6 | { 7 | "files": [ 8 | "*.json" 9 | ], 10 | "options": { 11 | "parser": "json-stringify" 12 | } 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Kleros 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ERC-792 2 | 3 | The repository contains definition of ERC-792 and ERC-1497 along with some examples and sources code of documentation at https://developer.kleros.io . 4 | -------------------------------------------------------------------------------- /contracts/IArbitrable.sol: -------------------------------------------------------------------------------- 1 | /** 2 | * @authors: [@ferittuncer, @hbarcelos] 3 | * @reviewers: [@remedcu] 4 | * @auditors: [] 5 | * @bounties: [] 6 | * @deployments: [] 7 | * SPDX-License-Identifier: MIT 8 | */ 9 | pragma solidity ^0.8.0; 10 | 11 | import "./IArbitrator.sol"; 12 | 13 | /** 14 | * @title IArbitrable 15 | * Arbitrable interface. 16 | * When developing arbitrable contracts, we need to: 17 | * - Define the action taken when a ruling is received by the contract. 18 | * - Allow dispute creation. For this a function must call arbitrator.createDispute{value: _fee}(_choices,_extraData); 19 | */ 20 | interface IArbitrable { 21 | /** 22 | * @dev To be raised when a ruling is given. 23 | * @param _arbitrator The arbitrator giving the ruling. 24 | * @param _disputeID ID of the dispute in the Arbitrator contract. 25 | * @param _ruling The ruling which was given. 26 | */ 27 | event Ruling(IArbitrator indexed _arbitrator, uint256 indexed _disputeID, uint256 _ruling); 28 | 29 | /** 30 | * @dev Give a ruling for a dispute. Must be called by the arbitrator. 31 | * The purpose of this function is to ensure that the address calling it has the right to rule on the contract. 32 | * @param _disputeID ID of the dispute in the Arbitrator contract. 33 | * @param _ruling Ruling given by the arbitrator. Note that 0 is reserved for "Not able/wanting to make a decision". 34 | */ 35 | function rule(uint256 _disputeID, uint256 _ruling) external; 36 | } 37 | -------------------------------------------------------------------------------- /contracts/IArbitrator.sol: -------------------------------------------------------------------------------- 1 | /** 2 | * @authors: [@ferittuncer, @hbarcelos] 3 | * @reviewers: [@remedcu] 4 | * @auditors: [] 5 | * @bounties: [] 6 | * @deployments: [] 7 | * SPDX-License-Identifier: MIT 8 | */ 9 | 10 | pragma solidity ^0.8.0; 11 | 12 | import "./IArbitrable.sol"; 13 | 14 | /** 15 | * @title Arbitrator 16 | * Arbitrator abstract contract. 17 | * When developing arbitrator contracts we need to: 18 | * - Define the functions for dispute creation (createDispute) and appeal (appeal). Don't forget to store the arbitrated contract and the disputeID (which should be unique, may nbDisputes). 19 | * - Define the functions for cost display (arbitrationCost and appealCost). 20 | * - Allow giving rulings. For this a function must call arbitrable.rule(disputeID, ruling). 21 | */ 22 | interface IArbitrator { 23 | enum DisputeStatus { 24 | Waiting, 25 | Appealable, 26 | Solved 27 | } 28 | 29 | /** 30 | * @dev To be emitted when a dispute is created. 31 | * @param _disputeID ID of the dispute. 32 | * @param _arbitrable The contract which created the dispute. 33 | */ 34 | event DisputeCreation(uint256 indexed _disputeID, IArbitrable indexed _arbitrable); 35 | 36 | /** 37 | * @dev To be emitted when a dispute can be appealed. 38 | * @param _disputeID ID of the dispute. 39 | * @param _arbitrable The contract which created the dispute. 40 | */ 41 | event AppealPossible(uint256 indexed _disputeID, IArbitrable indexed _arbitrable); 42 | 43 | /** 44 | * @dev To be emitted when the current ruling is appealed. 45 | * @param _disputeID ID of the dispute. 46 | * @param _arbitrable The contract which created the dispute. 47 | */ 48 | event AppealDecision(uint256 indexed _disputeID, IArbitrable indexed _arbitrable); 49 | 50 | /** 51 | * @dev Create a dispute. Must be called by the arbitrable contract. 52 | * Must be paid at least arbitrationCost(_extraData). 53 | * @param _choices Amount of choices the arbitrator can make in this dispute. 54 | * @param _extraData Can be used to give additional info on the dispute to be created. 55 | * @return disputeID ID of the dispute created. 56 | */ 57 | function createDispute(uint256 _choices, bytes calldata _extraData) external payable returns (uint256 disputeID); 58 | 59 | /** 60 | * @dev Compute the cost of arbitration. It is recommended not to increase it often, as it can be highly time and gas consuming for the arbitrated contracts to cope with fee augmentation. 61 | * @param _extraData Can be used to give additional info on the dispute to be created. 62 | * @return cost Amount to be paid. 63 | */ 64 | function arbitrationCost(bytes calldata _extraData) external view returns (uint256 cost); 65 | 66 | /** 67 | * @dev Appeal a ruling. Note that it has to be called before the arbitrator contract calls rule. 68 | * @param _disputeID ID of the dispute to be appealed. 69 | * @param _extraData Can be used to give extra info on the appeal. 70 | */ 71 | function appeal(uint256 _disputeID, bytes calldata _extraData) external payable; 72 | 73 | /** 74 | * @dev Compute the cost of appeal. It is recommended not to increase it often, as it can be higly time and gas consuming for the arbitrated contracts to cope with fee augmentation. 75 | * @param _disputeID ID of the dispute to be appealed. 76 | * @param _extraData Can be used to give additional info on the dispute to be created. 77 | * @return cost Amount to be paid. 78 | */ 79 | function appealCost(uint256 _disputeID, bytes calldata _extraData) external view returns (uint256 cost); 80 | 81 | /** 82 | * @dev Compute the start and end of the dispute's current or next appeal period, if possible. If not known or appeal is impossible: should return (0, 0). 83 | * @param _disputeID ID of the dispute. 84 | * @return start The start of the period. 85 | * @return end The end of the period. 86 | */ 87 | function appealPeriod(uint256 _disputeID) external view returns (uint256 start, uint256 end); 88 | 89 | /** 90 | * @dev Return the status of a dispute. 91 | * @param _disputeID ID of the dispute to rule. 92 | * @return status The status of the dispute. 93 | */ 94 | function disputeStatus(uint256 _disputeID) external view returns (DisputeStatus status); 95 | 96 | /** 97 | * @dev Return the current ruling of a dispute. This is useful for parties to know if they should appeal. 98 | * @param _disputeID ID of the dispute. 99 | * @return ruling The ruling which has been given or the one which will be given if there is no appeal. 100 | */ 101 | function currentRuling(uint256 _disputeID) external view returns (uint256 ruling); 102 | } 103 | -------------------------------------------------------------------------------- /contracts/erc-1497/IEvidence.sol: -------------------------------------------------------------------------------- 1 | /** 2 | * @authors: [@ferittuncer, @hbarcelos] 3 | * @reviewers: [] 4 | * @auditors: [] 5 | * @bounties: [] 6 | * @deployments: [] 7 | * SPDX-License-Identifier: MIT 8 | */ 9 | pragma solidity ^0.8.0; 10 | 11 | import "../IArbitrator.sol"; 12 | 13 | /** @title IEvidence 14 | * ERC-1497: Evidence Standard 15 | */ 16 | interface IEvidence { 17 | /** 18 | * @dev To be emitted when meta-evidence is submitted. 19 | * @param _metaEvidenceID Unique identifier of meta-evidence. 20 | * @param _evidence IPFS path to metaevidence, example: '/ipfs/Qmarwkf7C9RuzDEJNnarT3WZ7kem5bk8DZAzx78acJjMFH/metaevidence.json' 21 | */ 22 | event MetaEvidence(uint256 indexed _metaEvidenceID, string _evidence); 23 | 24 | /** 25 | * @dev To be raised when evidence is submitted. Should point to the resource (evidences are not to be stored on chain due to gas considerations). 26 | * @param _arbitrator The arbitrator of the contract. 27 | * @param _evidenceGroupID Unique identifier of the evidence group the evidence belongs to. 28 | * @param _party The address of the party submiting the evidence. Note that 0x0 refers to evidence not submitted by any party. 29 | * @param _evidence IPFS path to evidence, example: '/ipfs/Qmarwkf7C9RuzDEJNnarT3WZ7kem5bk8DZAzx78acJjMFH/evidence.json' 30 | */ 31 | event Evidence( 32 | IArbitrator indexed _arbitrator, 33 | uint256 indexed _evidenceGroupID, 34 | address indexed _party, 35 | string _evidence 36 | ); 37 | 38 | /** 39 | * @dev To be emitted when a dispute is created to link the correct meta-evidence to the disputeID. 40 | * @param _arbitrator The arbitrator of the contract. 41 | * @param _disputeID ID of the dispute in the Arbitrator contract. 42 | * @param _metaEvidenceID Unique identifier of meta-evidence. 43 | * @param _evidenceGroupID Unique identifier of the evidence group that is linked to this dispute. 44 | */ 45 | event Dispute( 46 | IArbitrator indexed _arbitrator, 47 | uint256 indexed _disputeID, 48 | uint256 _metaEvidenceID, 49 | uint256 _evidenceGroupID 50 | ); 51 | } 52 | -------------------------------------------------------------------------------- /contracts/examples/CentralizedArbitratorWithAppeal.sol: -------------------------------------------------------------------------------- 1 | /** 2 | * @authors: [@ferittuncer, @hbarcelos] 3 | * @reviewers: [] 4 | * @auditors: [] 5 | * @bounties: [] 6 | * @deployments: [] 7 | * SPDX-License-Identifier: MIT 8 | */ 9 | pragma solidity ^0.8.9; 10 | 11 | import "../IArbitrator.sol"; 12 | 13 | contract CentralizedArbitratorWithAppeal is IArbitrator { 14 | address public owner = msg.sender; 15 | uint256 constant appealWindow = 3 minutes; 16 | uint256 internal arbitrationFee = 1e15; 17 | 18 | error NotOwner(); 19 | error InsufficientPayment(uint256 _available, uint256 _required); 20 | error InvalidRuling(uint256 _ruling, uint256 _numberOfChoices); 21 | error InvalidStatus(DisputeStatus _current, DisputeStatus _expected); 22 | error BeforeAppealPeriodEnd(uint256 _currentTime, uint256 _appealPeriodEnd); 23 | error AfterAppealPeriodEnd(uint256 _currentTime, uint256 _appealPeriodEnd); 24 | 25 | struct Dispute { 26 | IArbitrable arbitrated; 27 | uint256 choices; 28 | uint256 ruling; 29 | DisputeStatus status; 30 | uint256 appealPeriodStart; 31 | uint256 appealPeriodEnd; 32 | uint256 appealCount; 33 | } 34 | 35 | Dispute[] public disputes; 36 | 37 | function arbitrationCost(bytes memory _extraData) public view override returns (uint256) { 38 | return arbitrationFee; 39 | } 40 | 41 | function appealCost(uint256 _disputeID, bytes memory _extraData) public view override returns (uint256) { 42 | return arbitrationFee * (2**(disputes[_disputeID].appealCount)); 43 | } 44 | 45 | function setArbitrationCost(uint256 _newCost) public { 46 | arbitrationFee = _newCost; 47 | } 48 | 49 | function createDispute(uint256 _choices, bytes memory _extraData) 50 | public 51 | payable 52 | override 53 | returns (uint256 disputeID) 54 | { 55 | uint256 requiredAmount = arbitrationCost(_extraData); 56 | if (msg.value > requiredAmount) { 57 | revert InsufficientPayment(msg.value, requiredAmount); 58 | } 59 | 60 | disputes.push( 61 | Dispute({ 62 | arbitrated: IArbitrable(msg.sender), 63 | choices: _choices, 64 | ruling: 0, 65 | status: DisputeStatus.Waiting, 66 | appealPeriodStart: 0, 67 | appealPeriodEnd: 0, 68 | appealCount: 0 69 | }) 70 | ); 71 | 72 | disputeID = disputes.length - 1; 73 | emit DisputeCreation(disputeID, IArbitrable(msg.sender)); 74 | } 75 | 76 | function disputeStatus(uint256 _disputeID) public view override returns (DisputeStatus status) { 77 | Dispute storage dispute = disputes[_disputeID]; 78 | if (disputes[_disputeID].status == DisputeStatus.Appealable && block.timestamp >= dispute.appealPeriodEnd) 79 | return DisputeStatus.Solved; 80 | else return disputes[_disputeID].status; 81 | } 82 | 83 | function currentRuling(uint256 _disputeID) public view override returns (uint256 ruling) { 84 | ruling = disputes[_disputeID].ruling; 85 | } 86 | 87 | function giveRuling(uint256 _disputeID, uint256 _ruling) public { 88 | if (msg.sender != owner) { 89 | revert NotOwner(); 90 | } 91 | 92 | Dispute storage dispute = disputes[_disputeID]; 93 | 94 | if (_ruling > dispute.choices) { 95 | revert InvalidRuling(_ruling, dispute.choices); 96 | } 97 | if (dispute.status != DisputeStatus.Waiting) { 98 | revert InvalidStatus(dispute.status, DisputeStatus.Waiting); 99 | } 100 | 101 | dispute.ruling = _ruling; 102 | dispute.status = DisputeStatus.Appealable; 103 | dispute.appealPeriodStart = block.timestamp; 104 | dispute.appealPeriodEnd = dispute.appealPeriodStart + appealWindow; 105 | 106 | emit AppealPossible(_disputeID, dispute.arbitrated); 107 | } 108 | 109 | function executeRuling(uint256 _disputeID) public { 110 | Dispute storage dispute = disputes[_disputeID]; 111 | if (dispute.status != DisputeStatus.Appealable) { 112 | revert InvalidStatus(dispute.status, DisputeStatus.Appealable); 113 | } 114 | 115 | if (block.timestamp <= dispute.appealPeriodEnd) { 116 | revert BeforeAppealPeriodEnd(block.timestamp, dispute.appealPeriodEnd); 117 | } 118 | 119 | dispute.status = DisputeStatus.Solved; 120 | dispute.arbitrated.rule(_disputeID, dispute.ruling); 121 | } 122 | 123 | function appeal(uint256 _disputeID, bytes memory _extraData) public payable override { 124 | Dispute storage dispute = disputes[_disputeID]; 125 | dispute.appealCount++; 126 | 127 | uint256 requiredAmount = appealCost(_disputeID, _extraData); 128 | if (msg.value < requiredAmount) { 129 | revert InsufficientPayment(msg.value, requiredAmount); 130 | } 131 | 132 | if (dispute.status != DisputeStatus.Appealable) { 133 | revert InvalidStatus(dispute.status, DisputeStatus.Appealable); 134 | } 135 | 136 | if (block.timestamp > dispute.appealPeriodEnd) { 137 | revert AfterAppealPeriodEnd(block.timestamp, dispute.appealPeriodEnd); 138 | } 139 | 140 | dispute.status = DisputeStatus.Waiting; 141 | 142 | emit AppealDecision(_disputeID, dispute.arbitrated); 143 | } 144 | 145 | function appealPeriod(uint256 _disputeID) public view override returns (uint256 start, uint256 end) { 146 | Dispute storage dispute = disputes[_disputeID]; 147 | 148 | return (dispute.appealPeriodStart, dispute.appealPeriodEnd); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /contracts/examples/Escrow.sol: -------------------------------------------------------------------------------- 1 | /** 2 | * @authors: [@ferittuncer, @hbarcelos] 3 | * @reviewers: [] 4 | * @auditors: [] 5 | * @bounties: [] 6 | * @deployments: [] 7 | * SPDX-License-Identifier: MIT 8 | */ 9 | pragma solidity ^0.8.9; 10 | 11 | import "../IArbitrable.sol"; 12 | import "../IArbitrator.sol"; 13 | import "../erc-1497/IEvidence.sol"; 14 | 15 | contract Escrow is IArbitrable, IEvidence { 16 | enum Status { 17 | Initial, 18 | Reclaimed, 19 | Disputed, 20 | Resolved 21 | } 22 | enum RulingOptions { 23 | RefusedToArbitrate, 24 | PayerWins, 25 | PayeeWins 26 | } 27 | uint256 constant numberOfRulingOptions = 2; 28 | 29 | error InvalidStatus(); 30 | error ReleasedTooEarly(); 31 | error NotPayer(); 32 | error NotArbitrator(); 33 | error ThirdPartyNotAllowed(); 34 | error PayeeDepositStillPending(); 35 | error ReclaimedTooLate(); 36 | error InsufficientPayment(uint256 _available, uint256 _required); 37 | error InvalidRuling(uint256 _ruling, uint256 _numberOfChoices); 38 | 39 | struct TX { 40 | address payable payer; 41 | address payable payee; 42 | IArbitrator arbitrator; 43 | Status status; 44 | uint256 value; 45 | uint256 disputeID; 46 | uint256 createdAt; 47 | uint256 reclaimedAt; 48 | uint256 payerFeeDeposit; 49 | uint256 payeeFeeDeposit; 50 | uint256 reclamationPeriod; 51 | uint256 arbitrationFeeDepositPeriod; 52 | } 53 | 54 | TX[] public txs; 55 | mapping(uint256 => uint256) disputeIDtoTXID; 56 | 57 | function newTransaction( 58 | address payable _payee, 59 | IArbitrator _arbitrator, 60 | string memory _metaevidence, 61 | uint256 _reclamationPeriod, 62 | uint256 _arbitrationFeeDepositPeriod 63 | ) public payable returns (uint256 txID) { 64 | emit MetaEvidence(txs.length, _metaevidence); 65 | 66 | txs.push( 67 | TX({ 68 | payer: payable(msg.sender), 69 | payee: _payee, 70 | arbitrator: _arbitrator, 71 | status: Status.Initial, 72 | value: msg.value, 73 | disputeID: 0, 74 | createdAt: block.timestamp, 75 | reclaimedAt: 0, 76 | payerFeeDeposit: 0, 77 | payeeFeeDeposit: 0, 78 | reclamationPeriod: _reclamationPeriod, 79 | arbitrationFeeDepositPeriod: _arbitrationFeeDepositPeriod 80 | }) 81 | ); 82 | 83 | txID = txs.length; 84 | } 85 | 86 | function releaseFunds(uint256 _txID) public { 87 | TX storage transaction = txs[_txID]; 88 | 89 | if (transaction.status != Status.Initial) { 90 | revert InvalidStatus(); 91 | } 92 | if ( 93 | msg.sender != transaction.payer && block.timestamp - transaction.createdAt <= transaction.reclamationPeriod 94 | ) { 95 | revert ReleasedTooEarly(); 96 | } 97 | 98 | transaction.status = Status.Resolved; 99 | transaction.payee.send(transaction.value); 100 | } 101 | 102 | function reclaimFunds(uint256 _txID) public payable { 103 | TX storage transaction = txs[_txID]; 104 | 105 | if (transaction.status != Status.Initial && transaction.status != Status.Reclaimed) { 106 | revert InvalidStatus(); 107 | } 108 | if (msg.sender != transaction.payer) { 109 | revert NotPayer(); 110 | } 111 | 112 | if (transaction.status == Status.Reclaimed) { 113 | if (block.timestamp - transaction.reclaimedAt <= transaction.arbitrationFeeDepositPeriod) { 114 | revert PayeeDepositStillPending(); 115 | } 116 | transaction.payer.send(transaction.value + transaction.payerFeeDeposit); 117 | transaction.status = Status.Resolved; 118 | } else { 119 | if (block.timestamp - transaction.createdAt > transaction.reclamationPeriod) { 120 | revert ReclaimedTooLate(); 121 | } 122 | 123 | uint256 requiredAmount = transaction.arbitrator.arbitrationCost(""); 124 | if (msg.value < requiredAmount) { 125 | revert InsufficientPayment(msg.value, requiredAmount); 126 | } 127 | 128 | transaction.payerFeeDeposit = msg.value; 129 | transaction.reclaimedAt = block.timestamp; 130 | transaction.status = Status.Reclaimed; 131 | } 132 | } 133 | 134 | function depositArbitrationFeeForPayee(uint256 _txID) public payable { 135 | TX storage transaction = txs[_txID]; 136 | 137 | if (transaction.status != Status.Reclaimed) { 138 | revert InvalidStatus(); 139 | } 140 | 141 | transaction.payeeFeeDeposit = msg.value; 142 | transaction.disputeID = transaction.arbitrator.createDispute{value: msg.value}(numberOfRulingOptions, ""); 143 | transaction.status = Status.Disputed; 144 | disputeIDtoTXID[transaction.disputeID] = _txID; 145 | emit Dispute(transaction.arbitrator, transaction.disputeID, _txID, _txID); 146 | } 147 | 148 | function rule(uint256 _disputeID, uint256 _ruling) public override { 149 | uint256 txID = disputeIDtoTXID[_disputeID]; 150 | TX storage transaction = txs[txID]; 151 | 152 | if (msg.sender != address(transaction.arbitrator)) { 153 | revert NotArbitrator(); 154 | } 155 | if (transaction.status != Status.Disputed) { 156 | revert InvalidStatus(); 157 | } 158 | if (_ruling > numberOfRulingOptions) { 159 | revert InvalidRuling(_ruling, numberOfRulingOptions); 160 | } 161 | transaction.status = Status.Resolved; 162 | 163 | if (_ruling == uint256(RulingOptions.PayerWins)) 164 | transaction.payer.send(transaction.value + transaction.payerFeeDeposit); 165 | else transaction.payee.send(transaction.value + transaction.payeeFeeDeposit); 166 | emit Ruling(transaction.arbitrator, _disputeID, _ruling); 167 | } 168 | 169 | function submitEvidence(uint256 _txID, string memory _evidence) public { 170 | TX storage transaction = txs[_txID]; 171 | 172 | if (transaction.status == Status.Resolved) { 173 | revert InvalidStatus(); 174 | } 175 | 176 | if (msg.sender != transaction.payer && msg.sender != transaction.payee) { 177 | revert ThirdPartyNotAllowed(); 178 | } 179 | emit Evidence(transaction.arbitrator, _txID, msg.sender, _evidence); 180 | } 181 | 182 | function remainingTimeToReclaim(uint256 _txID) public view returns (uint256) { 183 | TX storage transaction = txs[_txID]; 184 | 185 | if (transaction.status != Status.Initial) { 186 | revert InvalidStatus(); 187 | } 188 | return 189 | (block.timestamp - transaction.createdAt) > transaction.reclamationPeriod 190 | ? 0 191 | : (transaction.createdAt + transaction.reclamationPeriod - block.timestamp); 192 | } 193 | 194 | function remainingTimeToDepositArbitrationFee(uint256 _txID) public view returns (uint256) { 195 | TX storage transaction = txs[_txID]; 196 | 197 | if (transaction.status != Status.Reclaimed) { 198 | revert InvalidStatus(); 199 | } 200 | return 201 | (block.timestamp - transaction.reclaimedAt) > transaction.arbitrationFeeDepositPeriod 202 | ? 0 203 | : (transaction.reclaimedAt + transaction.arbitrationFeeDepositPeriod - block.timestamp); 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /contracts/examples/SimpleCentralizedArbitrator.sol: -------------------------------------------------------------------------------- 1 | /** 2 | * @authors: [@ferittuncer, @hbarcelos] 3 | * @reviewers: [] 4 | * @auditors: [] 5 | * @bounties: [] 6 | * @deployments: [] 7 | * SPDX-License-Identifier: MIT 8 | */ 9 | pragma solidity ^0.8.9; 10 | 11 | import "../IArbitrator.sol"; 12 | 13 | contract SimpleCentralizedArbitrator is IArbitrator { 14 | address public owner = msg.sender; 15 | 16 | error NotOwner(); 17 | error InsufficientPayment(uint256 _available, uint256 _required); 18 | error InvalidRuling(uint256 _ruling, uint256 _numberOfChoices); 19 | error InvalidStatus(DisputeStatus _current, DisputeStatus _expected); 20 | 21 | struct Dispute { 22 | IArbitrable arbitrated; 23 | uint256 choices; 24 | uint256 ruling; 25 | DisputeStatus status; 26 | } 27 | 28 | Dispute[] public disputes; 29 | 30 | function arbitrationCost(bytes memory _extraData) public pure override returns (uint256) { 31 | return 0.1 ether; 32 | } 33 | 34 | function appealCost(uint256 _disputeID, bytes memory _extraData) public pure override returns (uint256) { 35 | return 2**250; // An unaffordable amount which practically avoids appeals. 36 | } 37 | 38 | function createDispute(uint256 _choices, bytes memory _extraData) 39 | public 40 | payable 41 | override 42 | returns (uint256 disputeID) 43 | { 44 | uint256 requiredAmount = arbitrationCost(_extraData); 45 | if (msg.value < requiredAmount) { 46 | revert InsufficientPayment(msg.value, requiredAmount); 47 | } 48 | 49 | disputes.push( 50 | Dispute({arbitrated: IArbitrable(msg.sender), choices: _choices, ruling: 0, status: DisputeStatus.Waiting}) 51 | ); 52 | 53 | disputeID = disputes.length - 1; 54 | emit DisputeCreation(disputeID, IArbitrable(msg.sender)); 55 | } 56 | 57 | function disputeStatus(uint256 _disputeID) public view override returns (DisputeStatus status) { 58 | status = disputes[_disputeID].status; 59 | } 60 | 61 | function currentRuling(uint256 _disputeID) public view override returns (uint256 ruling) { 62 | ruling = disputes[_disputeID].ruling; 63 | } 64 | 65 | function rule(uint256 _disputeID, uint256 _ruling) public { 66 | if (msg.sender != owner) { 67 | revert NotOwner(); 68 | } 69 | 70 | Dispute storage dispute = disputes[_disputeID]; 71 | 72 | if (_ruling > dispute.choices) { 73 | revert InvalidRuling(_ruling, dispute.choices); 74 | } 75 | if (dispute.status != DisputeStatus.Waiting) { 76 | revert InvalidStatus(dispute.status, DisputeStatus.Waiting); 77 | } 78 | 79 | dispute.ruling = _ruling; 80 | dispute.status = DisputeStatus.Solved; 81 | 82 | payable(msg.sender).send(arbitrationCost("")); 83 | dispute.arbitrated.rule(_disputeID, _ruling); 84 | } 85 | 86 | function appeal(uint256 _disputeID, bytes memory _extraData) public payable override { 87 | uint256 requiredAmount = appealCost(_disputeID, _extraData); 88 | if (msg.value < requiredAmount) { 89 | revert InsufficientPayment(msg.value, requiredAmount); 90 | } 91 | } 92 | 93 | function appealPeriod(uint256 _disputeID) public pure override returns (uint256 start, uint256 end) { 94 | return (0, 0); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /contracts/examples/SimpleEscrow.sol: -------------------------------------------------------------------------------- 1 | /** 2 | * @authors: [@ferittuncer, @hbarcelos] 3 | * @reviewers: [] 4 | * @auditors: [] 5 | * @bounties: [] 6 | * @deployments: [] 7 | * SPDX-License-Identifier: MIT 8 | */ 9 | pragma solidity ^0.8.9; 10 | 11 | import "../IArbitrable.sol"; 12 | import "../IArbitrator.sol"; 13 | 14 | contract SimpleEscrow is IArbitrable { 15 | address payable public payer = payable(msg.sender); 16 | address payable public payee; 17 | uint256 public value; 18 | IArbitrator public arbitrator; 19 | string public agreement; 20 | uint256 public createdAt; 21 | uint256 public constant reclamationPeriod = 3 minutes; // Timeframe is short on purpose to be able to test it quickly. Not for production use. 22 | uint256 public constant arbitrationFeeDepositPeriod = 3 minutes; // Timeframe is short on purpose to be able to test it quickly. Not for production use. 23 | 24 | error InvalidStatus(); 25 | error ReleasedTooEarly(); 26 | error NotPayer(); 27 | error NotArbitrator(); 28 | error PayeeDepositStillPending(); 29 | error ReclaimedTooLate(); 30 | error InsufficientPayment(uint256 _available, uint256 _required); 31 | error InvalidRuling(uint256 _ruling, uint256 _numberOfChoices); 32 | 33 | enum Status { 34 | Initial, 35 | Reclaimed, 36 | Disputed, 37 | Resolved 38 | } 39 | Status public status; 40 | 41 | uint256 public reclaimedAt; 42 | 43 | enum RulingOptions { 44 | RefusedToArbitrate, 45 | PayerWins, 46 | PayeeWins 47 | } 48 | uint256 constant numberOfRulingOptions = 2; // Notice that option 0 is reserved for RefusedToArbitrate. 49 | 50 | constructor( 51 | address payable _payee, 52 | IArbitrator _arbitrator, 53 | string memory _agreement 54 | ) payable { 55 | value = msg.value; 56 | payee = _payee; 57 | arbitrator = _arbitrator; 58 | agreement = _agreement; 59 | createdAt = block.timestamp; 60 | } 61 | 62 | function releaseFunds() public { 63 | if (status != Status.Initial) { 64 | revert InvalidStatus(); 65 | } 66 | 67 | if (msg.sender != payer && block.timestamp - createdAt <= reclamationPeriod) { 68 | revert ReleasedTooEarly(); 69 | } 70 | 71 | status = Status.Resolved; 72 | payee.send(value); 73 | } 74 | 75 | function reclaimFunds() public payable { 76 | if (status != Status.Initial && status != Status.Reclaimed) { 77 | revert InvalidStatus(); 78 | } 79 | 80 | if (msg.sender != payer) { 81 | revert NotPayer(); 82 | } 83 | 84 | if (status == Status.Reclaimed) { 85 | if (block.timestamp - reclaimedAt <= arbitrationFeeDepositPeriod) { 86 | revert PayeeDepositStillPending(); 87 | } 88 | payer.send(address(this).balance); 89 | status = Status.Resolved; 90 | } else { 91 | if (block.timestamp - createdAt > reclamationPeriod) { 92 | revert ReclaimedTooLate(); 93 | } 94 | uint256 requiredAmount = arbitrator.arbitrationCost(""); 95 | if (msg.value < requiredAmount) { 96 | revert InsufficientPayment(msg.value, requiredAmount); 97 | } 98 | reclaimedAt = block.timestamp; 99 | status = Status.Reclaimed; 100 | } 101 | } 102 | 103 | function depositArbitrationFeeForPayee() public payable { 104 | if (status != Status.Reclaimed) { 105 | revert InvalidStatus(); 106 | } 107 | arbitrator.createDispute{value: msg.value}(numberOfRulingOptions, ""); 108 | status = Status.Disputed; 109 | } 110 | 111 | function rule(uint256 _disputeID, uint256 _ruling) public override { 112 | if (msg.sender != address(arbitrator)) { 113 | revert NotArbitrator(); 114 | } 115 | if (status != Status.Disputed) { 116 | revert InvalidStatus(); 117 | } 118 | if (_ruling > numberOfRulingOptions) { 119 | revert InvalidRuling(_ruling, numberOfRulingOptions); 120 | } 121 | 122 | status = Status.Resolved; 123 | if (_ruling == uint256(RulingOptions.PayerWins)) payer.send(address(this).balance); 124 | else if (_ruling == uint256(RulingOptions.PayeeWins)) payee.send(address(this).balance); 125 | emit Ruling(arbitrator, _disputeID, _ruling); 126 | } 127 | 128 | function remainingTimeToReclaim() public view returns (uint256) { 129 | if (status != Status.Initial) { 130 | revert InvalidStatus(); 131 | } 132 | return 133 | (block.timestamp - createdAt) > reclamationPeriod ? 0 : (createdAt + reclamationPeriod - block.timestamp); 134 | } 135 | 136 | function remainingTimeToDepositArbitrationFee() public view returns (uint256) { 137 | if (status != Status.Reclaimed) { 138 | revert InvalidStatus(); 139 | } 140 | return 141 | (block.timestamp - reclaimedAt) > arbitrationFeeDepositPeriod 142 | ? 0 143 | : (reclaimedAt + arbitrationFeeDepositPeriod - block.timestamp); 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /contracts/examples/SimpleEscrowWithERC1497.sol: -------------------------------------------------------------------------------- 1 | /** 2 | * @authors: [@ferittuncer, @hbarcelos] 3 | * @reviewers: [] 4 | * @auditors: [] 5 | * @bounties: [] 6 | * @deployments: [] 7 | * SPDX-License-Identifier: MIT 8 | */ 9 | pragma solidity ^0.8.9; 10 | 11 | import "../IArbitrable.sol"; 12 | import "../IArbitrator.sol"; 13 | import "../erc-1497/IEvidence.sol"; 14 | 15 | contract SimpleEscrowWithERC1497 is IArbitrable, IEvidence { 16 | address payable public payer = payable(msg.sender); 17 | address payable public payee; 18 | uint256 public value; 19 | IArbitrator public arbitrator; 20 | uint256 public constant reclamationPeriod = 3 minutes; // Timeframe is short on purpose to be able to test it quickly. Not for production use. 21 | uint256 public constant arbitrationFeeDepositPeriod = 3 minutes; // Timeframe is short on purpose to be able to test it quickly. Not for production use. 22 | 23 | uint256 public createdAt; 24 | uint256 public reclaimedAt; 25 | 26 | enum Status { 27 | Initial, 28 | Reclaimed, 29 | Disputed, 30 | Resolved 31 | } 32 | Status public status; 33 | 34 | enum RulingOptions { 35 | RefusedToArbitrate, 36 | PayerWins, 37 | PayeeWins 38 | } 39 | uint256 constant numberOfRulingOptions = 2; 40 | 41 | uint256 constant metaevidenceID = 0; 42 | uint256 constant evidenceGroupID = 0; 43 | 44 | error InvalidStatus(); 45 | error ReleasedTooEarly(); 46 | error NotPayer(); 47 | error NotArbitrator(); 48 | error ThirdPartyNotAllowed(); 49 | error PayeeDepositStillPending(); 50 | error ReclaimedTooLate(); 51 | error InsufficientPayment(uint256 _available, uint256 _required); 52 | error InvalidRuling(uint256 _ruling, uint256 _numberOfChoices); 53 | error UnexistingDispute(); 54 | 55 | constructor( 56 | address payable _payee, 57 | IArbitrator _arbitrator, 58 | string memory _metaevidence 59 | ) payable { 60 | value = msg.value; 61 | payee = _payee; 62 | arbitrator = _arbitrator; 63 | createdAt = block.timestamp; 64 | 65 | emit MetaEvidence(metaevidenceID, _metaevidence); 66 | } 67 | 68 | function releaseFunds() public { 69 | require(status == Status.Initial, "Transaction is not in Initial state."); 70 | 71 | if (msg.sender != payer && block.timestamp - createdAt <= reclamationPeriod) { 72 | revert ReleasedTooEarly(); 73 | } 74 | 75 | status = Status.Resolved; 76 | payee.send(value); 77 | } 78 | 79 | function reclaimFunds() public payable { 80 | if (status != Status.Initial && status != Status.Reclaimed) { 81 | revert InvalidStatus(); 82 | } 83 | if (msg.sender != payer) { 84 | revert NotPayer(); 85 | } 86 | 87 | if (status == Status.Reclaimed) { 88 | if (block.timestamp - reclaimedAt <= arbitrationFeeDepositPeriod) { 89 | revert PayeeDepositStillPending(); 90 | } 91 | payer.send(address(this).balance); 92 | status = Status.Resolved; 93 | } else { 94 | if (block.timestamp - createdAt > reclamationPeriod) { 95 | revert ReclaimedTooLate(); 96 | } 97 | uint256 requiredCost = arbitrator.arbitrationCost(""); 98 | if (msg.value < requiredCost) { 99 | revert InsufficientPayment(msg.value, requiredCost); 100 | } 101 | reclaimedAt = block.timestamp; 102 | status = Status.Reclaimed; 103 | } 104 | } 105 | 106 | function depositArbitrationFeeForPayee() public payable { 107 | if (status != Status.Reclaimed) { 108 | revert InvalidStatus(); 109 | } 110 | uint256 disputeID = arbitrator.createDispute{value: msg.value}(numberOfRulingOptions, ""); 111 | status = Status.Disputed; 112 | emit Dispute(arbitrator, disputeID, metaevidenceID, evidenceGroupID); 113 | } 114 | 115 | function rule(uint256 _disputeID, uint256 _ruling) public override { 116 | if (msg.sender != address(arbitrator)) { 117 | revert NotArbitrator(); 118 | } 119 | if (status != Status.Disputed) { 120 | revert UnexistingDispute(); 121 | } 122 | if (_ruling > numberOfRulingOptions) { 123 | revert InvalidRuling(_ruling, numberOfRulingOptions); 124 | } 125 | 126 | status = Status.Resolved; 127 | if (_ruling == uint256(RulingOptions.PayerWins)) payer.send(address(this).balance); 128 | else payee.send(address(this).balance); 129 | emit Ruling(arbitrator, _disputeID, _ruling); 130 | } 131 | 132 | function remainingTimeToReclaim() public view returns (uint256) { 133 | if (status != Status.Initial) revert("Transaction is not in Initial state."); 134 | return 135 | (createdAt + reclamationPeriod - block.timestamp) > reclamationPeriod 136 | ? 0 137 | : (createdAt + reclamationPeriod - block.timestamp); 138 | } 139 | 140 | function remainingTimeToDepositArbitrationFee() public view returns (uint256) { 141 | if (status != Status.Reclaimed) revert("Transaction is not in Reclaimed state."); 142 | return 143 | (reclaimedAt + arbitrationFeeDepositPeriod - block.timestamp) > arbitrationFeeDepositPeriod 144 | ? 0 145 | : (reclaimedAt + arbitrationFeeDepositPeriod - block.timestamp); 146 | } 147 | 148 | function submitEvidence(string memory _evidence) public { 149 | if (status == Status.Resolved) { 150 | revert InvalidStatus(); 151 | } 152 | if (msg.sender != payer && msg.sender != payee) { 153 | revert ThirdPartyNotAllowed(); 154 | } 155 | emit Evidence(arbitrator, evidenceGroupID, msg.sender, _evidence); 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SOURCEDIR = . 8 | BUILDDIR = build 9 | 10 | # Put it first so that "make" without argument is like "make help". 11 | help: 12 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 13 | 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/a-simple-dapp.rst: -------------------------------------------------------------------------------- 1 | ************* 2 | A Simple DApp 3 | ************* 4 | 5 | .. note:: 6 | This tutorial requires basic Javascript programming skills and basic understanding of React Framework. 7 | 8 | .. note:: 9 | You can find the finished React project `source code here `_. You can test it `live here `_. 10 | 11 | 12 | Let's implement a simple decentralized application using ``SimpleEscrowWithERC1497`` contract. 13 | 14 | We will create the simplest possible UI, as front-end development is out of the scope of this tutorial. 15 | 16 | Tools used in this tutorial: 17 | 18 | * Yarn 19 | * React 20 | * Create React App 21 | * Bootstrap 22 | * IPFS 23 | * MetaMask 24 | 25 | 26 | Arbitrable Side 27 | ############### 28 | 29 | Scaffolding The Project And Installing Dependencies 30 | *************************************************** 31 | 32 | 1. Run ``yarn create react-app a-simple-dapp`` to create a directory "a-simple-dapp" under your working directory and scaffold your application. 33 | 34 | 2. Run ``yarn add web3@1.0.0-beta.37 react-bootstrap`` to install required dependencies. Using exact versions for web3 and ipfs-http-client is recommended. 35 | 36 | 3. Add the following Bootstrap styleshet in ``index.html`` 37 | 38 | .. code-block:: javascript 39 | 40 | 46 | 47 | 4. Inside the application directory, running ``yarn start`` should run your application now. By default it runs on `port 3000 `_. 48 | 49 | 50 | 51 | Ethereum Interface 52 | ****************** 53 | 54 | Under the ``src`` directory, let's create a directory called ``ethereum`` for Ethereum-related files. 55 | 56 | Setting Up Web3 57 | =============== 58 | 59 | 60 | Let's create a new file called ``web3.js`` under ``ethereum`` directory. We will put a helper inside it which will let us access MetaMask for sending transactions and querying the blockchain. For more details please see `the MetaMask documentation `_ . 61 | 62 | 63 | .. literalinclude:: ../src/ethereum/web3.js 64 | :language: javascript 65 | :caption: web3.js 66 | :name: web3 67 | 68 | Preparing Helper Functions For SimpleEscrowWithERC1497 And Arbitrator Contracts 69 | =============================================================================== 70 | 71 | We need to call functions of ``SimpleEscrowWithERC1497`` and the arbitrator (for ``arbitrationCost``, to be able to send the correct amount when creating a dispute), so we need helpers for them. 72 | 73 | We will import build artifacts of ``SimpleEscrowWithERC1497`` and ``Arbitrator`` contracts to use their ABIs (`application binary interface `_). 74 | So we copy those under ``ethereum`` directory and create two helper files (``arbitrator.js`` and ``simple-escrow-with-erc1497.js``) using each of them. 75 | 76 | 77 | 78 | .. literalinclude:: ../src/ethereum/simple-escrow-with-erc1497.js 79 | :language: javascript 80 | :caption: simple-escrow-with-erc1497.js 81 | :name: simple-escrow-with-erc1497 82 | 83 | .. literalinclude:: ../src/ethereum/arbitrator.js 84 | :language: javascript 85 | :caption: arbitrator.js 86 | :name: arbitrator 87 | 88 | Evidence and Meta-Evidence Helpers 89 | ================================== 90 | 91 | Recall `Evidence Standard `_ JSON format. These two javascript object factories will be used to create JSON objects according to the standard. 92 | 93 | .. literalinclude:: ../src/ethereum/generate-evidence.js 94 | :language: javascript 95 | :caption: generate-evidence.js 96 | :name: generate-evidence 97 | 98 | .. literalinclude:: ../src/ethereum/generate-meta-evidence.js 99 | :language: javascript 100 | :caption: generate-meta-evidence.js 101 | :name: generate-meta-evidence 102 | 103 | Evidence Storage 104 | **************** 105 | 106 | We want to make sure evidence files are tamper-proof. So we need an immutable file storage. `IPFS `_ is perfect fit for this use-case. 107 | The following helper will let us publish evidence on IPFS, through the IPFS node at https://ipfs.kleros.io . 108 | 109 | .. literalinclude:: ../src/ipfs-publish.js 110 | :language: javascript 111 | :caption: ipfs-publish.js 112 | :name: ipfs-publish 113 | 114 | 115 | React Components 116 | **************** 117 | 118 | We will create a single-page react application to keep it simple. The main component, ``App`` will contain two sub-components: 119 | 120 | * ``Deploy`` 121 | * ``Interact`` 122 | 123 | ``Deploy`` component will contain a form for arguments of ``SimpleEscrowWithERC1497`` deployment and a deploy button. 124 | 125 | ``Interact`` component will have an input field for entering a contract address that is deployed already, to interact with. It will also have badges to show some state variable values of the contract. 126 | In addition, it will have three buttons for three main functions: ``releaseFunds``, ``reclaimFunds`` and ``depositArbitrationFeeForPayee``. 127 | Lastly, it will have a file picker and submit button for submitting evidence. 128 | 129 | ``App`` will be responsible for accessing Ethereum. So it will give callbacks to ``Deploy`` and ``Interact`` to let them access Ethereum through ``App``. 130 | 131 | App 132 | === 133 | .. literalinclude:: ../src/app.js 134 | :language: jsx 135 | :caption: app.js 136 | :name: app 137 | 138 | Deploy 139 | ====== 140 | 141 | .. literalinclude:: ../src/deploy.js 142 | :language: jsx 143 | :caption: deploy.js 144 | :name: deploy 145 | 146 | Interact 147 | ======== 148 | .. literalinclude:: ../src/interact.js 149 | :language: jsx 150 | :caption: interact.js 151 | :name: interact 152 | 153 | 154 | Arbitrator Side 155 | ############### 156 | 157 | To interact with an arbitrator, we can use `Centralized Arbitrator Dashboard `_. It let's setting up an arbitrator easily and provides UI to interact with, very useful for debugging and testing arbitrable implementations. As arbitrator, it deploys `AutoAppealableArbitrator `_ which is very similar to the one we developed in the tutorials. 158 | 159 | To Use Centralized Arbitrator Dashboard (CAD): 160 | 161 | 1. Deploy a new arbitrator by specifying arbitration fee, choose a tiny amount for convenience, like `0.001` Ether. 162 | 2. Copy the arbitrator address and use this address as the arbitrator, in your arbitrable contract. 163 | 3. Create a dispute on your arbitrable contract. 164 | 4. Go back to CAD, select the arbitrator you created in the first step, by entering the contract address. 165 | 5. Now you should be able to see the dispute you created. You can give rulings to it using CAD. 166 | 167 | Alternatively, you can use `Kleros Arbitrator on Kovan network `_ for testing. In that case, use this arbitrator address in your arbitrable contract, then simply go to https://court.kleros.io and switch your web3 provider to Kovan network. To be able to stake in a court, you will need Kovan PNK token, which you can buy from https://court.kleros.io/tokens. 168 | 169 | Finally, when your arbitrable contract is ready, use `Kleros Arbitrator on main network `_ to integrate with Kleros. 170 | -------------------------------------------------------------------------------- /docs/arbitrable.rst: -------------------------------------------------------------------------------- 1 | ==================== 2 | Arbitrable Interface 3 | ==================== 4 | 5 | .. literalinclude:: ../contracts/IArbitrable.sol 6 | :language: javascript 7 | 8 | 9 | ``rule`` is the function to be called by ``Arbitrator`` to enforce a *ruling* to a *dispute*. 10 | 11 | ``Ruling`` is the event which has to be emitted whenever a *final ruling* is given. For example, inside ``rule`` function, where the ruling is final and gets enforced. 12 | -------------------------------------------------------------------------------- /docs/arbitrator.rst: -------------------------------------------------------------------------------- 1 | ==================== 2 | Arbitrator Interface 3 | ==================== 4 | 5 | 6 | .. literalinclude:: ../contracts/IArbitrator.sol 7 | :language: javascript 8 | 9 | 10 | 11 | Dispute Status 12 | ############## 13 | 14 | There are three statuses that the function ``disputeStatus`` can return; ``Waiting``, ``Appealable`` and ``Solved``: 15 | 16 | 17 | * A *dispute* is in ``Waiting`` state when it arises (gets created, by ``createDispute`` function). 18 | 19 | * Is in ``Appealable`` state when it got a *ruling* and the ``Arbitrator`` allows to *appeal* it. When the ``Arbitrator`` allows to appeal, it often gives a time period to do so. If a dispute is not appealed within that time, ``disputeStatus`` should return ``Solved``. 20 | 21 | * Is in ``Solved`` state when it got a *ruling* and the *ruling* is final. Note that this doesn't imply ``rule`` function on the ``Arbitrable`` has been called to enforce (execute) the *ruling*. It means that the decision on the *dispute* is final and to be executed. 22 | 23 | 24 | Events 25 | ###### 26 | 27 | There are three events to be emitted: 28 | 29 | * ``DisputeCreation`` when a *dispute* gets created by ``createDispute`` function. 30 | 31 | * ``AppealPossible`` when appealing a *dispute* becomes possible. 32 | 33 | * ``AppealDecision`` when *current ruling* is *appealed*. 34 | 35 | 36 | Functions 37 | ######### 38 | 39 | And seven functions: 40 | 41 | * ``createDispute`` should create a dispute with given number of possible ``_choices`` for decisions. ``_extraData`` is for passing any extra information for any kind of custom handling. 42 | While calling ``createDispute``, caller has to pass required *arbitration fee*, otherwise ``createDispute`` should revert. ``createDispute`` should be called by an ``Arbitrable``. Lastly, it should emit ``DisputeCreation`` event. 43 | 44 | * ``arbitrationCost`` should return the *arbitration cost* that is required to *create a dispute*, in weis. 45 | 46 | * ``appeal`` should appeal a dispute and should require the caller to pass the required *appeal fee*. ``appeal`` should be called by an ``Arbitrable`` and should emit the ``AppealDecision`` event. 47 | 48 | * ``appealCost`` should return the *appeal fee* that is required to *appeal*, in weis. 49 | 50 | * ``appealPeriod`` should return the time window, in ``(start, end)`` format, for appealing a ruling, if known in advance. If not known or appeal is impossible: should return ``(0, 0)``. 51 | 52 | * ``disputeStatus`` should return the status of dispute; ``Waiting``, ``Appealable`` or ``Solved``. 53 | 54 | * ``currentRuling`` should return the current ruling of a dispute. 55 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # http://www.sphinx-doc.org/en/master/config 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | # import os 14 | # import sys 15 | # sys.path.insert(0, os.path.abspath('.')) 16 | 17 | 18 | # -- Project information ----------------------------------------------------- 19 | 20 | project = 'ERC-792: Arbitration Standard' 21 | copyright = '2019, Kleros' 22 | author = 'Kleros' 23 | 24 | # The full version, including alpha/beta/rc tags 25 | release = '1.0.0' 26 | 27 | master_doc = 'index' 28 | 29 | # -- General configuration --------------------------------------------------- 30 | 31 | # Add any Sphinx extension module names here, as strings. They can be 32 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 33 | # ones. 34 | extensions = [ 35 | 'sphinx.ext.todo' 36 | ] 37 | 38 | [extensions] 39 | todo_include_todos=True 40 | 41 | # Add any paths that contain templates here, relative to this directory. 42 | templates_path = ['_templates'] 43 | 44 | # List of patterns, relative to source directory, that match files and 45 | # directories to ignore when looking for source files. 46 | # This pattern also affects html_static_path and html_extra_path. 47 | exclude_patterns = [] 48 | 49 | 50 | # -- Options for HTML output ------------------------------------------------- 51 | 52 | # The theme to use for HTML and HTML Help pages. See the documentation for 53 | # a list of builtin themes. 54 | # 55 | html_theme = 'default' 56 | 57 | # Add any paths that contain custom static files (such as style sheets) here, 58 | # relative to this directory. They are copied after the builtin static files, 59 | # so a file named "default.css" will overwrite the builtin "default.css". 60 | html_static_path = ['_static'] 61 | -------------------------------------------------------------------------------- /docs/erc-1497.rst: -------------------------------------------------------------------------------- 1 | =============================== 2 | ERC-1497: Evidence Standard 3 | =============================== 4 | 5 | .. note:: See the original proposal of the standard `here `_. 6 | 7 | .. warning:: 8 | Smart contracts in this tutorial are not intended for production but educational purposes. Beware of using them on main network. 9 | 10 | In ``SimpleEscrow`` contract, we used a ``string`` to store the agreement between the parties. The deployer could format that ``string`` anyway they like, as 11 | there are many ways to signal the information about the agreement. 12 | 13 | Instead, having a standard would allow interoperability. That's why *ERC-1497: Evidence Standard* describes a standard approach for this. It has two categories of information: evidence and meta-evidence. 14 | 15 | Evidence, as the name hints, is a piece of information to support a proposition. 16 | Meta-evidence is the information about the dispute itself: the agreement, parties involved, the thing that is to be decided, ruling options etc. 17 | 18 | ERC-1497 introduces three new events: MetaEvidence, Evidence and Dispute. 19 | 20 | .. image:: erc1497.png 21 | 22 | .. literalinclude:: ../contracts/erc-1497/IEvidence.sol 23 | :language: javascript 24 | 25 | * ``event MetaEvidence`` provides the context of the dispute, the question the arbitrators have to answer, the human readable meanings of rulings and specific modes of display for evidence. We use ``_metaEvidenceID`` to uniquely identify a piece of meta-evidence. This is necessary when there are multiple meta-evidence use-cases. ``_evidence`` contains the URI for meta-evidence JSON file. 26 | * ``event Evidence`` links a piece of evidence with an arbitrator, sending party and dispute. ``_evidence`` contains the URI for evidence JSON file. 27 | * ``event Dispute`` is raised when a dispute is created to link the proper meta-evidence and evidence group to the dispute. The event includes a reference to the arbitrator, a unique identifier for the dispute itself, the identifier to look up the meta-evidence event log and the identifier of the evidence group that can be used to look up all evidence submitted in the grouping. 28 | 29 | .. note:: See the `original proposal `_ for standard evidence and meta-evidence JSON formats. 30 | 31 | Let's return to ``SimpleEscrow`` and refactor it to implement ERC-1497 interface. Recall ``SimpleEscrow``: 32 | 33 | .. literalinclude:: ../contracts/examples/SimpleEscrow.sol 34 | :language: javascript 35 | 36 | Now, first let's implement `IEvidence`: 37 | 38 | .. code-block:: javascript 39 | :emphasize-lines: 5,7 40 | 41 | pragma solidity ^0.5; 42 | 43 | import "../IArbitrable.sol"; 44 | import "../Arbitrator.sol"; 45 | import "../erc-1497/IEvidence.sol"; 46 | 47 | contract SimpleEscrowWithERC1497 is IArbitrable, IEvidence { 48 | address payable public payer = msg.sender; 49 | address payable public payee; 50 | uint public value; 51 | Arbitrator public arbitrator; 52 | string public agreement; 53 | uint public createdAt; 54 | uint constant public reclamationPeriod = 3 minutes; 55 | uint constant public arbitrationFeeDepositPeriod = 3 minutes; 56 | 57 | 58 | enum Status {Initial, Reclaimed, Disputed, Resolved} 59 | Status public status; 60 | 61 | uint public reclaimedAt; 62 | 63 | enum RulingOptions {PayerWins, PayeeWins, Count} 64 | 65 | constructor(address payable _payee, Arbitrator _arbitrator, string memory _agreement) payable { 66 | value = msg.value; 67 | payee = _payee; 68 | arbitrator = _arbitrator; 69 | agreement = _agreement; 70 | createdAt = block.timestamp; 71 | } 72 | 73 | function releaseFunds() public { 74 | require(status == Status.Initial, "Transaction is not in initial status."); 75 | 76 | if(msg.sender != payer) 77 | require(block.timestamp - createdAt > reclamationPeriod, "Payer still has time to reclaim."); 78 | 79 | status = Status.Resolved; 80 | payee.send(value); 81 | } 82 | 83 | function reclaimFunds() public payable { 84 | require(status == Status.Initial || status == Status.Reclaimed, "Status should be initial or reclaimed."); 85 | require(msg.sender == payer, "Only the payer can reclaim the funds."); 86 | 87 | if(status == Status.Reclaimed){ 88 | require(block.timestamp - reclaimedAt > arbitrationFeeDepositPeriod, "Payee still has time to deposit arbitration fee."); 89 | payer.send(address(this).balance); 90 | status = Status.Resolved; 91 | } 92 | else{ 93 | require(block.timestamp - createdAt < reclamationPeriod, "Reclamation period ended."); 94 | require(msg.value == arbitrator.arbitrationCost(""), "Can't reclaim funds without depositing arbitration fee."); 95 | reclaimedAt = block.timestamp; 96 | status = Status.Reclaimed; 97 | } 98 | } 99 | 100 | function depositArbitrationFeeForPayee() public payable { 101 | require(status == Status.Reclaimed, "Payer didn't reclaim, nothing to dispute."); 102 | arbitrator.createDispute{value: msg.value}(uint(RulingOptions.Count), ""); 103 | status = Status.Disputed; 104 | } 105 | 106 | function rule(uint _disputeID, uint _ruling) public { 107 | require(msg.sender == address(arbitrator), "Only the arbitrator can execute this."); 108 | require(status == Status.Disputed, "There should be dispute to execute a ruling."); 109 | status = Status.Resolved; 110 | if(_ruling == uint(RulingOptions.PayerWins)) payer.send(address(this).balance); 111 | else payee.send(address(this).balance); 112 | emit Ruling(arbitrator, _disputeID, _ruling); 113 | } 114 | 115 | function remainingTimeToReclaim() public view returns (uint) { 116 | if(status != Status.Initial) revert("Transaction is not in initial state."); 117 | return (createdAt + reclamationPeriod - block.timestamp) > reclamationPeriod ? 0 : (createdAt + reclamationPeriod - block.timestamp); 118 | } 119 | 120 | function remainingTimeToDepositArbitrationFee() public view returns (uint) { 121 | if (status != Status.Reclaimed) revert("Funds are not reclaimed."); 122 | return (reclaimedAt + arbitrationFeeDepositPeriod - block.timestamp) > arbitrationFeeDepositPeriod ? 0 : (reclaimedAt + arbitrationFeeDepositPeriod - block.timestamp); 123 | } 124 | } 125 | 126 | 127 | And then, we will get rid of ``string agreement``. Instead we need ``uint metaevidenceID``, ``string _metaevidence`` that contains the URI to metaevidence JSON that is formatted according to the standard and we have to emit ``MetaEvidence`` event. 128 | 129 | .. literalinclude:: ../contracts/examples/SimpleEscrowWithERC1497.sol 130 | :language: javascript 131 | :lines: 1-24,26-65,67-86 132 | :emphasize-lines: 24,26,32 133 | 134 | 135 | We set the identifier of meta-evidence to constant zero, as there won't be multiple meta-evidence for this contract. Any constant number would do the job. Then we emit ``MetaEvidence`` with the provided 136 | ``_metaevidence``. This string contains the URI from where the content of meta-evidence can be fetched. 137 | 138 | Also, we need to emit ``Dispute`` when we create a new dispute: 139 | 140 | .. literalinclude:: ../contracts/examples/SimpleEscrowWithERC1497.sol 141 | :language: javascript 142 | :lines: 1-86 143 | :emphasize-lines: 25,67 144 | 145 | 146 | There will be only one dispute in this contract so we can use a constant zero for ``evidenceGroupID``. 147 | 148 | Lastly, we need a function to let parties submit evidence: 149 | 150 | .. literalinclude:: ../contracts/examples/SimpleEscrowWithERC1497.sol 151 | :language: javascript 152 | :emphasize-lines: 90-95 153 | 154 | Congratulations, now your arbitrable is ERC-1497 compatible! 155 | -------------------------------------------------------------------------------- /docs/erc1497.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kleros/erc-792/87bea72ec0a4c92ce7f62e596e84f24be7662dd7/docs/erc1497.png -------------------------------------------------------------------------------- /docs/erc792.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kleros/erc-792/87bea72ec0a4c92ce7f62e596e84f24be7662dd7/docs/erc792.png -------------------------------------------------------------------------------- /docs/implementing-a-complex-arbitrable.rst: -------------------------------------------------------------------------------- 1 | ================================= 2 | Implementing a Complex Arbitrable 3 | ================================= 4 | 5 | .. warning:: 6 | Smart contracts in this tutorial are not intended for production but educational purposes. Beware of using them on main network. 7 | 8 | Let's implement a full-fledged escrow this time, extending ``SimpleEscrowWithERC1497`` contract we implemented earlier. We will call it just ``Escrow`` this time. 9 | 10 | Recall ``SimpleEscrowWithERC1497``: 11 | 12 | .. literalinclude:: ../contracts/examples/SimpleEscrowWithERC1497.sol 13 | :language: javascript 14 | 15 | 16 | The payer needs to deploy a contract for each transaction, but contract deployment is expensive. 17 | Instead, we could use the same contract for multiple transactions between arbitrary parties with arbitrary arbitrators. 18 | 19 | Let's separate contract deployment and transaction creation: 20 | 21 | .. literalinclude:: ../contracts/examples/Escrow.sol 22 | :language: javascript 23 | :emphasize-lines: 9- 24 | 25 | 26 | We first start by removing the global state variables and defining ``TX`` struct. Each instance of this struct will represent a transaction, thus will have transaction-specific variables instead of globals. 27 | We stored transactions inside ``txs`` array. We also created new transactions via ``newTransaction`` function. 28 | 29 | ``newTransaction`` function simply takes transaction-specific information and pushes a ``TX`` into ``txs``. This ``txs`` array is append-only, we will never remove any item. 30 | By implementing this, we can uniquely identify each transaction by their index in the array. 31 | 32 | Next, we updated all the functions with transaction-specific variables instead of globals. Changes are merely adding ``tx.`` prefixes in front of expressions. 33 | 34 | We also stored fee deposits for each party, as the smart contract now has balances for multiple transactions that we can't ``send(address(this).balance)``. 35 | Instead, we used ``tx.payer.send(tx.value + tx.payerFeeDeposit);`` if ``payer`` wins and ``tx.payee.send(tx.value + tx.payeeFeeDeposit);`` if ``payee`` wins. 36 | 37 | Notice that ``rule`` function has no transaction ID parameter, but we need to obtain transaction details of given dispute. We achieved this by storing the transaction ID for respective dispute ID as ``disputeIDtoTXID``. 38 | Just after dispute creation (inside ``depositArbitrationFeeForPayee``), we store this relation with ``disputeIDtoTXID[tx.disputeID] = _txID;`` statement. 39 | 40 | Good job! Now we have an escrow contract which can handle multiple transactions between different parties and arbitrators. 41 | -------------------------------------------------------------------------------- /docs/implementing-a-complex-arbitrator.rst: -------------------------------------------------------------------------------- 1 | ================================= 2 | Implementing a Complex Arbitrator 3 | ================================= 4 | 5 | .. warning:: 6 | Smart contracts in this tutorial are not intended for production but educational purposes. Beware of using them on main network. 7 | 8 | We will refactor ``SimpleCentralizedArbitrator`` to add appeal functionality and dynamic costs. 9 | 10 | Recall ``SimpleCentralizedArbitrator``: 11 | 12 | .. literalinclude:: ../contracts/examples/SimpleCentralizedArbitrator.sol 13 | :language: javascript 14 | 15 | First, let's implement the appeal: 16 | 17 | .. code-block:: javascript 18 | :emphasize-lines: 8,15,16,36,37,44-48,55-94 19 | 20 | pragma solidity ^0.5; 21 | 22 | import "../Arbitrator.sol"; 23 | 24 | contract CentralizedArbitratorWithAppeal is Arbitrator { 25 | 26 | address public owner = msg.sender; 27 | uint constant appealWindow = 3 minutes; 28 | 29 | struct Dispute { 30 | IArbitrable arbitrated; 31 | uint choices; 32 | uint ruling; 33 | DisputeStatus status; 34 | uint appealPeriodStart; 35 | uint appealPeriodEnd; 36 | } 37 | 38 | Dispute[] public disputes; 39 | 40 | function arbitrationCost(bytes memory _extraData) public view returns(uint fee) { 41 | fee = 0.1 ether; 42 | } 43 | 44 | function appealCost(uint _disputeID, bytes memory _extraData) public view returns(uint fee) { 45 | fee = 2**250; // An unaffordable amount which practically avoids appeals. 46 | } 47 | 48 | function createDispute(uint _choices, bytes memory _extraData) public payable returns(uint disputeID) { 49 | super.createDispute(_choices, _extraData); 50 | disputeID = disputes.push(Dispute({ 51 | arbitrated: IArbitrable(msg.sender), 52 | choices: _choices, 53 | ruling: 0, 54 | status: DisputeStatus.Waiting, 55 | appealPeriodStart: 0, 56 | appealPeriodEnd: 0 57 | })); 58 | 59 | emit DisputeCreation(disputeID, IArbitrable(msg.sender)); 60 | } 61 | 62 | function disputeStatus(uint _disputeID) public view returns(DisputeStatus status) { 63 | Dispute storage dispute = disputes[_disputeID]; 64 | if (disputes[_disputeID].status == DisputeStatus.Appealable && block.timestamp >= dispute.appealPeriodEnd) 65 | return DisputeStatus.Solved; 66 | else 67 | return disputes[_disputeID].status; 68 | } 69 | 70 | function currentRuling(uint _disputeID) public view returns(uint ruling) { 71 | ruling = disputes[_disputeID].ruling; 72 | } 73 | 74 | function giveRuling(uint _disputeID, uint _ruling) public { 75 | require(msg.sender == owner, "Only the owner of this contract can execute rule function."); 76 | 77 | Dispute storage dispute = disputes[_disputeID]; 78 | 79 | require(_ruling <= dispute.choices, "Ruling out of bounds!"); 80 | require(dispute.status != DisputeStatus.Solved, "Can't rule an already solved dispute!"); 81 | 82 | dispute.ruling = _ruling; 83 | dispute.status = DisputeStatus.Appealable; 84 | dispute.appealPeriodStart = block.timestamp; 85 | dispute.appealPeriodEnd = dispute.appealPeriodStart + appealWindow; 86 | } 87 | 88 | function executeRuling(uint _disputeID) public { 89 | Dispute storage dispute = disputes[_disputeID]; 90 | require(dispute.status == DisputeStatus.Appealable, "The dispute must be appealable."); 91 | require(block.timestamp >= dispute.appealPeriodEnd, "The dispute must be executed after its appeal period has ended."); 92 | 93 | dispute.status = DisputeStatus.Solved; 94 | dispute.arbitrated.rule(_disputeID, dispute.ruling); 95 | } 96 | 97 | function appeal(uint _disputeID, bytes memory _extraData) public payable { 98 | Dispute storage dispute = disputes[_disputeID]; 99 | 100 | super.appeal(_disputeID, _extraData); 101 | 102 | require(dispute.status == DisputeStatus.Appealable, "The dispute must be appealable."); 103 | require(block.timestamp < dispute.appealPeriodEnd, "The appeal must occur before the end of the appeal period."); 104 | 105 | dispute.status = DisputeStatus.Waiting; 106 | } 107 | 108 | function appealPeriod(uint _disputeID) public view returns(uint start, uint end) { 109 | Dispute storage dispute = disputes[_disputeID]; 110 | 111 | return (dispute.appealPeriodStart, dispute.appealPeriodEnd); 112 | } 113 | } 114 | 115 | 116 | 117 | We first define ``appealWindow`` constant, which is the amount of time a dispute stays appealable. 118 | 119 | To implement ``appealPeriod`` function of the ERC-792 interface, we define two additional variables in ``Dispute`` struct: ``appealPeriodStart`` and ``appealPeriodEnd``. 120 | 121 | ``DisputeStatus`` function is also updated to handle the case where a dispute has ``DisputeStatus.Appealable`` status, but the appeal window is closed, so actually it is ``DisputeStatus.Solved``. 122 | 123 | The important change is we divided proxy ``rule`` function into two parts. 124 | 125 | - ``giveRuling``: Gives ruling, but does not enforce it. 126 | - ``executeRuling`` Enforces ruling, only after the appeal window is closed. 127 | 128 | Before, there was no appeal functionality, so we didn't have to wait for appeal and ruling was enforced immediately after giving the ruling. Now we need to do them separately. 129 | 130 | ``appeal`` function checks whether the dispute is eligible for appeal and performs the appeal by setting ``status`` back to the default value, ``DisputeStatus.Waiting``. 131 | 132 | 133 | Now let's revisit cost functions: 134 | 135 | 136 | .. literalinclude:: ../contracts/examples/CentralizedArbitratorWithAppeal.sol 137 | :language: javascript 138 | :emphasize-lines: 9, 18, 24, 28, 31-33, 44, 90 139 | 140 | 141 | 142 | We implemented a setter for arbitration cost and we made the appeal cost as exponentially increasing. 143 | We achieved that by counting the number of appeals with ``appealCount`` variable, which gets increased each time ``appeal`` is executed. 144 | 145 | This concludes our implementation of a centralized arbitrator with appeal functionality. 146 | -------------------------------------------------------------------------------- /docs/implementing-an-arbitrable.rst: -------------------------------------------------------------------------------- 1 | ========================== 2 | Implementing an Arbitrable 3 | ========================== 4 | 5 | .. warning:: 6 | Smart contracts in this tutorial are not intended for production but educational purposes. Beware of using them on main network. 7 | 8 | 9 | When developing arbitrable contracts, we need to: 10 | 11 | * Implement ``rule`` function to define an action to be taken when a ruling is received by the contract. 12 | * Develop a logic to create disputes (via calling ``createDispute`` on Arbitrable) 13 | 14 | Consider a case where two parties trade ether for goods. Payer wants to pay only if payee provides promised goods. So payer deposits payment amount into an escrow and if a dispute arises an arbitrator will resolve it. 15 | 16 | There will be two scenarios: 17 | 1. No dispute arises, ``payee`` withdraws the funds. 18 | 2. ``payer`` reclaims funds by depositing arbitration fee... 19 | a. ``payee`` fails to deposit arbitration fee in ``arbitrationFeeDepositPeriod`` and ``payer`` wins by default. The arbitration fee deposit paid by ``payer`` refunded. 20 | b. ``payee`` deposits arbitration fee in time. Dispute gets created. ``arbitrator`` rules. Winner gets the arbitration fee refunded. 21 | 22 | Notice that only in scenario 2b ``arbitrator`` intervenes. In other scenarios we don't create a dispute thus don't await for a ruling. 23 | Also, notice that in case of a dispute, the winning side gets reimbursed for the arbitration fee deposit. So in effect, the loser will be paying for the arbitration. 24 | 25 | 26 | 27 | 28 | 29 | 30 | Let's start: 31 | 32 | 33 | .. literalinclude:: ../contracts/examples/SimpleEscrow.sol 34 | :language: javascript 35 | :lines: 1-14,24-30 36 | 37 | 38 | ``payer`` deploys the contract depositing the payment amount and specifying ``payee`` address, ``arbitrator`` that is authorized to rule and ``agreement`` string. Notice that ``payer = msg.sender``. 39 | 40 | We made ``reclamationPeriod`` and ``arbitrationFeeDepositPeriod`` constant for sake of simplicity, they could be set by ``payer`` in the constructor too. 41 | 42 | Let's implement the first scenario: 43 | 44 | 45 | .. literalinclude:: ../contracts/examples/SimpleEscrow.sol 46 | :language: javascript 47 | :lines: 1-19,24-41 48 | :emphasize-lines: 17,18,29-37 49 | 50 | 51 | In ``releaseFunds`` function, first we do state checks: transaction should be in ``Status.Initial`` and ``reclamationPeriod`` should be passed unless the caller is ``payer``. 52 | If so, we update ``status`` to ``Status.Resolved`` and send the funds to ``payee``. 53 | 54 | Moving forward to second scenario: 55 | 56 | .. literalinclude:: ../contracts/examples/SimpleEscrow.sol 57 | :language: javascript 58 | :emphasize-lines: 20,22,23,42- 59 | 60 | 61 | ``reclaimFunds`` function lets ``payer`` to reclaim their funds. After ``payer`` calls this function for the first time the window (``arbitrationFeeDepositPeriod``) for ``payee`` to deposit arbitration fee starts. 62 | If they fail to deposit in time, ``payer`` can call the function for the second time and get the funds back. 63 | In case if ``payee`` deposits the arbitration fee in time a *dispute* gets created and the contract awaits arbitrator's decision. 64 | 65 | We define enforcement of rulings in ``rule`` function. Whoever wins the dispute should get the funds and should get reimbursed for the arbitration fee. 66 | Recall that we took the arbitration fee deposit from both sides and used one of them to pay for the arbitrator. Thus the balance of the contract is at least funds plus arbitration fee. Therefore we send ``address(this).balance`` to the winner. Lastly, we emit ``Ruling`` as required in the standard. 67 | 68 | And also we have two view functions to get remaining times, which will be useful for front-end development. 69 | 70 | That's it! We implemented a very simple escrow using ERC-792. 71 | -------------------------------------------------------------------------------- /docs/implementing-an-arbitrator.rst: -------------------------------------------------------------------------------- 1 | ========================== 2 | Implementing an Arbitrator 3 | ========================== 4 | 5 | .. warning:: 6 | Smart contracts in this tutorial are not intended for production but educational purposes. Beware of using them on main network. 7 | 8 | When developing arbitrator contracts we need to: 9 | 10 | * Implement the functions ``createDispute`` and ``appeal``. Don't forget to store the arbitrated contract and the disputeID (which should be unique). 11 | * Implement the functions for cost display (``arbitrationCost`` and ``appealCost``). 12 | * Allow enforcing rulings. For this a function must execute ``arbitrable.rule(disputeID, ruling)``. 13 | 14 | 15 | To demonstrate how to use the standard, we will implement a very simple arbitrator where a single address gives rulings and there aren't any appeals. 16 | 17 | Let's start by implementing cost functions: 18 | 19 | .. literalinclude:: ../contracts/examples/SimpleCentralizedArbitrator.sol 20 | :language: javascript 21 | :lines: 1-5,17-25 22 | 23 | 24 | We set the arbitration fee to ``0.1 ether`` and the appeal fee to an astronomical amount which can't be afforded. 25 | So in practice, we disabled appeal, for simplicity. We made costs constant, again, for the sake of simplicity of this tutorial. 26 | 27 | Next, we need a data structure to keep track of disputes: 28 | 29 | 30 | .. literalinclude:: ../contracts/examples/SimpleCentralizedArbitrator.sol 31 | :language: javascript 32 | :lines: 1-5,8-25 33 | :emphasize-lines: 7-14 34 | 35 | 36 | 37 | Each dispute belongs to an ``Arbitrable`` contract, so we have ``arbitrated`` field for it. 38 | Each dispute will have a ruling stored in ``ruling`` field: For example, Party A wins (represented by ``ruling = 1``) and Party B wins (represented by ``ruling = 2``), recall that ``ruling = 0`` is reserved for "refused to arbitrate". 39 | We also store number of ruling options in ``choices`` to be able to avoid undefined rulings in the proxy function which executes ``arbitrable.rule(disputeID, ruling)``. 40 | Finally, each dispute will have a status, and we store it inside ``status`` field. 41 | 42 | Next, we can implement the function for creating disputes: 43 | 44 | 45 | .. literalinclude:: ../contracts/examples/SimpleCentralizedArbitrator.sol 46 | :language: javascript 47 | :lines: 1-5,8-37 48 | :emphasize-lines: 24-35 49 | 50 | Note that ``createDispute`` function should be called by an *arbitrable*. 51 | 52 | We require the caller to pay at least ``arbitrationCost(_extraData)``. We could send back the excess payment, but we omitted it for the sake of simplicity. 53 | 54 | Then, we create the dispute by pushing a new element to the array: ``disputes.push( ... )``. 55 | The ``push`` function returns the resulting size of the array, thus we can use the return value of ``disputes.push( ... ) -1`` as ``disputeID`` starting from zero. 56 | Finally, we emit ``DisputeCreation`` as required in the standard. 57 | 58 | We also need to implement getters for ``status`` and ``ruling``: 59 | 60 | 61 | 62 | .. literalinclude:: ../contracts/examples/SimpleCentralizedArbitrator.sol 63 | :language: javascript 64 | :lines: 1-5,8-50 65 | :emphasize-lines: 36-48 66 | 67 | 68 | 69 | Finally, we need a proxy function to call ``rule`` function of the ``Arbitrable`` contract. In this simple ``Arbitrator`` we will let one address, the creator of the contract, to give rulings. So let's start by storing contract creator's address: 70 | 71 | .. literalinclude:: ../contracts/examples/SimpleCentralizedArbitrator.sol 72 | :language: javascript 73 | :lines: 1-45 74 | :emphasize-lines: 7 75 | 76 | 77 | Then the proxy function: 78 | 79 | .. literalinclude:: ../contracts/examples/SimpleCentralizedArbitrator.sol 80 | :language: javascript 81 | :lines: 1-60 82 | :emphasize-lines: 46- 83 | 84 | 85 | First we check the caller address, we should only let the ``owner`` execute this. Then we do sanity checks: the ruling given by the arbitrator should be chosen among the ``choices`` and it should not be possible to ``rule`` on an already solved dispute. 86 | Afterwards, we update ``ruling`` and ``status`` values of the dispute. Then we pay arbitration fee to the arbitrator (``owner``). Finally, we call ``rule`` function of the ``arbitrated`` to enforce the ruling. 87 | 88 | 89 | Lastly, appeal functions: 90 | 91 | 92 | .. literalinclude:: ../contracts/examples/SimpleCentralizedArbitrator.sol 93 | :language: javascript 94 | :emphasize-lines: 61- 95 | 96 | Just a dummy implementation to conform the interface, as we don't actually implement appeal functionality. 97 | 98 | That's it, we have a working, very simple centralized arbitrator! 99 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | ERC-792: Arbitration Standard 2 | ================================= 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | :caption: Contents: 7 | 8 | introduction 9 | arbitrable 10 | arbitrator 11 | implementing-an-arbitrable 12 | implementing-an-arbitrator 13 | erc-1497 14 | a-simple-dapp 15 | implementing-a-complex-arbitrable 16 | implementing-a-complex-arbitrator 17 | wrap-up 18 | -------------------------------------------------------------------------------- /docs/introduction.rst: -------------------------------------------------------------------------------- 1 | =============== 2 | Introduction 3 | =============== 4 | 5 | .. note:: 6 | This tutorial requires basic Solidity programming skills. 7 | 8 | .. note:: See the original proposal of the standard `here `_. 9 | 10 | ERC-792: Arbitration Standard proposes a standard for arbitration on Ethereum. The standard has two types of smart contracts: ``Arbitrable`` and ``Arbitrator``. 11 | 12 | ``Arbitrable`` contracts are the contracts on which *rulings* of the authorized ``Arbitrator`` are enforceable. 13 | 14 | ``Arbitrator`` contracts are the contracts which give *rulings* on disputes. 15 | 16 | In other words, ``Arbitrator`` gives rulings, and ``Arbitrable`` enforces them. 17 | 18 | .. image:: erc792.png 19 | 20 | In the following topics, you will be guided through the usage of the standard. We will implement some examples for ``Arbitrable`` and ``Arbitrator`` contracts. 21 | 22 | .. note:: 23 | Highlighted sections in source code examples indicate statements that are just modified. 24 | 25 | 26 | .. note:: 27 | You can find the `contracts used in this tutorial here `_. 28 | 29 | We will also implement a very simple decentralized application on top of an ``Arbitrable`` we developed. You can see the `live demo of the DAPP we will develop, here `_. 30 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | jsx-lexer 2 | -------------------------------------------------------------------------------- /docs/wrap-up.rst: -------------------------------------------------------------------------------- 1 | =================== 2 | Summary and Wrap-Up 3 | =================== 4 | 5 | In this tutorial, we explained `ERC 792 `_ and `ERC 1497 `_ standards, implemented arbitrable and arbitrator contracts following the standards and built a simple React application with ``SimpleEscrowWithERC1497`` we developed in the tutorials. 6 | 7 | All the contracts we developed in this documentation can be found under `the contracts `_ directory and the source code of the React application resides under `the src `_ directory. 8 | 9 | Important links: 10 | 11 | * https://github.com/kleros/kleros 12 | * https://github.com/kleros/kleros-interaction 13 | * https://centralizedarbitrator.kleros.io/ You can use this to test your arbitrable contract against. 14 | 15 | Any questions? Feel free to reach out to us on `Slack `_ or `Discord `_ . 16 | -------------------------------------------------------------------------------- /hardhat.config.js: -------------------------------------------------------------------------------- 1 | require("@nomiclabs/hardhat-waffle"); 2 | 3 | module.exports = { 4 | solidity: { 5 | version: "0.8.9", 6 | settings: { 7 | optimizer: { 8 | enabled: true, 9 | runs: 200, 10 | }, 11 | }, 12 | }, 13 | }; 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@kleros/erc-792", 3 | "version": "8.0.0", 4 | "description": "ERC-792: Arbitration Standard", 5 | "main": "index.js", 6 | "repository": "https://github.com/kleros/erc-792", 7 | "author": "Kleros", 8 | "license": "MIT", 9 | "private": false, 10 | "devDependencies": { 11 | "@nomiclabs/hardhat-ethers": "^2.0.0", 12 | "@nomiclabs/hardhat-waffle": "^2.0.0", 13 | "chai": "^4.2.0", 14 | "ethereum-waffle": "^3.0.0", 15 | "ethers": "^5.0.0", 16 | "hardhat": "^2.6.5", 17 | "husky": "^6.0.0", 18 | "ipfs-http-client": "32.0.1", 19 | "lint-staged": "^11.2.0", 20 | "npm-run-all": "^4.1.5", 21 | "prettier": "^2.4.1", 22 | "prettier-plugin-solidity": "^1.0.0-beta.18", 23 | "react": "16.8.6", 24 | "react-bootstrap": "1.0.0-beta.8", 25 | "react-dom": "16.8.6", 26 | "react-scripts": "3.0.1", 27 | "web3": "^1.6.0" 28 | }, 29 | "scripts": { 30 | "sol:build": "hardhat compile", 31 | "start": "react-scripts start", 32 | "build": "react-scripts build", 33 | "test": "react-scripts test", 34 | "eject": "react-scripts eject", 35 | "prepare": "husky install" 36 | }, 37 | "files": [ 38 | "contracts" 39 | ], 40 | "eslintConfig": { 41 | "extends": "react-app" 42 | }, 43 | "browserslist": { 44 | "production": [ 45 | ">0.2%", 46 | "not dead", 47 | "not op_mini all" 48 | ], 49 | "development": [ 50 | "last 1 chrome version", 51 | "last 1 firefox version", 52 | "last 1 safari version" 53 | ] 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kleros/erc-792/87bea72ec0a4c92ce7f62e596e84f24be7662dd7/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 19 | 28 | React App 29 | 30 | 31 | 32 |
33 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /src/app.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import web3 from "./ethereum/web3"; 3 | import generateEvidence from "./ethereum/generate-evidence"; 4 | import generateMetaevidence from "./ethereum/generate-meta-evidence"; 5 | import * as SimpleEscrowWithERC1497 from "./ethereum/simple-escrow-with-erc1497"; 6 | import * as Arbitrator from "./ethereum/arbitrator"; 7 | import Ipfs from "ipfs-http-client"; 8 | import ipfsPublish from "./ipfs-publish"; 9 | 10 | import Container from "react-bootstrap/Container"; 11 | import Jumbotron from "react-bootstrap/Jumbotron"; 12 | import Button from "react-bootstrap/Button"; 13 | import Form from "react-bootstrap/Form"; 14 | import Row from "react-bootstrap/Row"; 15 | import Col from "react-bootstrap/Col"; 16 | import Deploy from "./deploy.js"; 17 | import Interact from "./interact.js"; 18 | 19 | class App extends React.Component { 20 | constructor(props) { 21 | super(props); 22 | this.state = { 23 | activeAddress: "0x0000000000000000000000000000000000000000", 24 | lastDeployedAddress: "0x0000000000000000000000000000000000000000", 25 | }; 26 | this.ipfs = new Ipfs({ 27 | host: "ipfs.kleros.io", 28 | port: 5001, 29 | protocol: "https", 30 | }); 31 | } 32 | 33 | deploy = async (amount, payee, arbitrator, title, description) => { 34 | const { activeAddress } = this.state; 35 | 36 | let metaevidence = generateMetaevidence( 37 | web3.utils.toChecksumAddress(activeAddress), 38 | web3.utils.toChecksumAddress(payee), 39 | amount, 40 | title, 41 | description 42 | ); 43 | const enc = new TextEncoder(); 44 | const ipfsHashMetaEvidenceObj = await ipfsPublish("metaEvidence.json", enc.encode(JSON.stringify(metaevidence))); 45 | 46 | let result = await SimpleEscrowWithERC1497.deploy( 47 | activeAddress, 48 | payee, 49 | amount, 50 | arbitrator, 51 | 52 | "/ipfs/" + ipfsHashMetaEvidenceObj[1]["hash"] + ipfsHashMetaEvidenceObj[0]["path"] 53 | ); 54 | 55 | this.setState({ lastDeployedAddress: result._address }); 56 | }; 57 | 58 | load = (contractAddress) => SimpleEscrowWithERC1497.contractInstance(contractAddress); 59 | 60 | reclaimFunds = async (contractAddress, value) => { 61 | const { activeAddress } = this.state; 62 | await SimpleEscrowWithERC1497.reclaimFunds(activeAddress, contractAddress, value); 63 | }; 64 | 65 | releaseFunds = async (contractAddress) => { 66 | const { activeAddress } = this.state; 67 | 68 | await SimpleEscrowWithERC1497.releaseFunds(activeAddress, contractAddress); 69 | }; 70 | 71 | depositArbitrationFeeForPayee = (contractAddress, value) => { 72 | const { activeAddress } = this.state; 73 | 74 | SimpleEscrowWithERC1497.depositArbitrationFeeForPayee(activeAddress, contractAddress, value); 75 | }; 76 | 77 | reclamationPeriod = (contractAddress) => SimpleEscrowWithERC1497.reclamationPeriod(contractAddress); 78 | 79 | arbitrationFeeDepositPeriod = (contractAddress) => 80 | SimpleEscrowWithERC1497.arbitrationFeeDepositPeriod(contractAddress); 81 | 82 | remainingTimeToReclaim = (contractAddress) => SimpleEscrowWithERC1497.remainingTimeToReclaim(contractAddress); 83 | 84 | remainingTimeToDepositArbitrationFee = (contractAddress) => 85 | SimpleEscrowWithERC1497.remainingTimeToDepositArbitrationFee(contractAddress); 86 | 87 | arbitrationCost = (arbitratorAddress, extraData) => Arbitrator.arbitrationCost(arbitratorAddress, extraData); 88 | 89 | arbitrator = (contractAddress) => SimpleEscrowWithERC1497.arbitrator(contractAddress); 90 | 91 | status = (contractAddress) => SimpleEscrowWithERC1497.status(contractAddress); 92 | 93 | value = (contractAddress) => SimpleEscrowWithERC1497.value(contractAddress); 94 | 95 | submitEvidence = async (contractAddress, evidenceBuffer) => { 96 | const { activeAddress } = this.state; 97 | 98 | const result = await ipfsPublish("name", evidenceBuffer); 99 | 100 | let evidence = generateEvidence("/ipfs/" + result[0]["hash"], "name", "description"); 101 | const enc = new TextEncoder(); 102 | const ipfsHashEvidenceObj = await ipfsPublish("evidence.json", enc.encode(JSON.stringify(evidence))); 103 | 104 | SimpleEscrowWithERC1497.submitEvidence(contractAddress, activeAddress, "/ipfs/" + ipfsHashEvidenceObj[0]["hash"]); 105 | }; 106 | 107 | async componentDidMount() { 108 | if (window.ethereum && window.ethereum.isMetaMask) 109 | window.ethereum.request({ method: "eth_requestAccounts" }).then((accounts) => { 110 | this.setState({ activeAddress: accounts[0] }); 111 | }); 112 | else console.error("MetaMask account not detected :("); 113 | 114 | window.ethereum.on("accountsChanged", (accounts) => { 115 | this.setState({ activeAddress: accounts[0] }); 116 | }); 117 | } 118 | 119 | render() { 120 | const { lastDeployedAddress } = this.state; 121 | return ( 122 | 123 | 124 | 125 |

A Simple DAPP Using SimpleEscrowWithERC1497

126 | 127 |
128 | 129 | 130 | 131 | 132 | 133 | 134 | 148 | 149 | 150 | 151 | 152 |
153 | 154 |

Need to interact with your arbitrator contract?

155 |

156 | We have a general purpose user interface for centralized arbitrators (like we have developed in the 157 | tutorial) already. 158 |

159 |

160 | 163 |

164 |
165 |
166 | 167 |
168 |
169 | ); 170 | } 171 | } 172 | 173 | export default App; 174 | -------------------------------------------------------------------------------- /src/deploy.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Container from 'react-bootstrap/Container' 3 | import Form from 'react-bootstrap/Form' 4 | import Button from 'react-bootstrap/Button' 5 | import Card from 'react-bootstrap/Card' 6 | 7 | class Deploy extends React.Component { 8 | constructor(props) { 9 | super(props) 10 | this.state = { 11 | amount: '', 12 | payee: '', 13 | arbitrator: '', 14 | title: '', 15 | description: '' 16 | } 17 | } 18 | 19 | onAmountChange = e => { 20 | this.setState({ amount: e.target.value }) 21 | } 22 | 23 | onPayeeChange = e => { 24 | this.setState({ payee: e.target.value }) 25 | } 26 | 27 | onArbitratorChange = e => { 28 | this.setState({ arbitrator: e.target.value }) 29 | } 30 | 31 | onTitleChange = e => { 32 | this.setState({ title: e.target.value }) 33 | } 34 | 35 | onDescriptionChange = e => { 36 | this.setState({ description: e.target.value }) 37 | } 38 | 39 | onDeployButtonClick = async e => { 40 | e.preventDefault() 41 | const { amount, payee, arbitrator, title, description } = this.state 42 | console.log(arbitrator) 43 | await this.props.deployCallback( 44 | amount, 45 | payee, 46 | arbitrator, 47 | title, 48 | description 49 | ) 50 | } 51 | render() { 52 | const { amount, payee, arbitrator, title, description } = this.state 53 | 54 | return ( 55 | 56 | 57 | 58 | Deploy 59 |
60 | 61 | 68 | 69 | 70 | 77 | 78 | 79 | 86 | 87 | 88 | 95 | 96 | 97 | 104 | 105 | 113 |
114 |
115 |
116 |
117 | ) 118 | } 119 | } 120 | 121 | export default Deploy 122 | -------------------------------------------------------------------------------- /src/ethereum/arbitrator.js: -------------------------------------------------------------------------------- 1 | import Arbitrator from './arbitrator.json' 2 | import web3 from './web3' 3 | 4 | export const contractInstance = address => 5 | new web3.eth.Contract(Arbitrator.abi, address) 6 | 7 | export const arbitrationCost = (instanceAddress, extraData) => 8 | contractInstance(instanceAddress) 9 | .methods.arbitrationCost(web3.utils.utf8ToHex(extraData)) 10 | .call() 11 | -------------------------------------------------------------------------------- /src/ethereum/generate-evidence.js: -------------------------------------------------------------------------------- 1 | export default (fileURI, name, description) => ({ 2 | fileURI, 3 | name, 4 | description 5 | }) 6 | -------------------------------------------------------------------------------- /src/ethereum/generate-meta-evidence.js: -------------------------------------------------------------------------------- 1 | export default (payer, payee, amount, title, description) => ({ 2 | category: 'Escrow', 3 | title: title, 4 | description: description, 5 | question: 'Does payer deserves to be refunded?', 6 | rulingOptions: { 7 | type: 'single-select', 8 | titles: ['Refund the Payer', 'Pay the Payee'], 9 | descriptions: [ 10 | 'Select to return funds to the payer', 11 | 'Select to release funds to the payee' 12 | ] 13 | }, 14 | aliases: { 15 | [payer]: 'payer', 16 | [payee]: 'payee' 17 | }, 18 | amount 19 | }) 20 | -------------------------------------------------------------------------------- /src/ethereum/simple-escrow-with-erc1497.js: -------------------------------------------------------------------------------- 1 | import SimpleEscrowWithERC1497 from './simple-escrow-with-erc1497.json' 2 | import web3 from './web3' 3 | 4 | export const contractInstance = address => 5 | new web3.eth.Contract(SimpleEscrowWithERC1497.abi, address) 6 | 7 | export const deploy = (payer, payee, amount, arbitrator, metaevidence) => 8 | new web3.eth.Contract(SimpleEscrowWithERC1497.abi) 9 | .deploy({ 10 | arguments: [payee, arbitrator, metaevidence], 11 | data: SimpleEscrowWithERC1497.bytecode 12 | }) 13 | .send({ from: payer, value: amount }) 14 | 15 | export const reclaimFunds = (senderAddress, instanceAddress, value) => 16 | contractInstance(instanceAddress) 17 | .methods.reclaimFunds() 18 | .send({ from: senderAddress, value }) 19 | 20 | export const releaseFunds = (senderAddress, instanceAddress) => 21 | contractInstance(instanceAddress) 22 | .methods.releaseFunds() 23 | .send({ from: senderAddress }) 24 | 25 | export const depositArbitrationFeeForPayee = ( 26 | senderAddress, 27 | instanceAddress, 28 | value 29 | ) => 30 | contractInstance(instanceAddress) 31 | .methods.depositArbitrationFeeForPayee() 32 | .send({ from: senderAddress, value }) 33 | 34 | export const reclamationPeriod = instanceAddress => 35 | contractInstance(instanceAddress) 36 | .methods.reclamationPeriod() 37 | .call() 38 | 39 | export const arbitrationFeeDepositPeriod = instanceAddress => 40 | contractInstance(instanceAddress) 41 | .methods.arbitrationFeeDepositPeriod() 42 | .call() 43 | 44 | export const createdAt = instanceAddress => 45 | contractInstance(instanceAddress) 46 | .methods.createdAt() 47 | .call() 48 | 49 | export const remainingTimeToReclaim = instanceAddress => 50 | contractInstance(instanceAddress) 51 | .methods.remainingTimeToReclaim() 52 | .call() 53 | 54 | export const remainingTimeToDepositArbitrationFee = instanceAddress => 55 | contractInstance(instanceAddress) 56 | .methods.remainingTimeToDepositArbitrationFee() 57 | .call() 58 | 59 | export const arbitrator = instanceAddress => 60 | contractInstance(instanceAddress) 61 | .methods.arbitrator() 62 | .call() 63 | 64 | export const status = instanceAddress => 65 | contractInstance(instanceAddress) 66 | .methods.status() 67 | .call() 68 | 69 | export const value = instanceAddress => 70 | contractInstance(instanceAddress) 71 | .methods.value() 72 | .call() 73 | 74 | export const submitEvidence = (instanceAddress, senderAddress, evidence) => 75 | contractInstance(instanceAddress) 76 | .methods.submitEvidence(evidence) 77 | .send({ from: senderAddress }) 78 | -------------------------------------------------------------------------------- /src/ethereum/web3.js: -------------------------------------------------------------------------------- 1 | import Web3 from 'web3' 2 | 3 | let web3 4 | 5 | window.addEventListener('load', async () => { 6 | // Modern dapp browsers... 7 | if (window.ethereum) { 8 | window.web3 = new Web3(window.ethereum) 9 | try { 10 | // Request account access if needed 11 | await window.ethereum.enable() 12 | // Acccounts now exposed 13 | } catch (_) { 14 | // User denied account access... 15 | } 16 | } 17 | // Legacy dapp browsers... 18 | else if (window.web3) window.web3 = new Web3(web3.currentProvider) 19 | // Acccounts always exposed 20 | // Non-dapp browsers... 21 | else 22 | console.log( 23 | 'Non-Ethereum browser detected. You should consider trying MetaMask!' 24 | ) 25 | }) 26 | 27 | if (typeof window !== 'undefined' && typeof window.web3 !== 'undefined') { 28 | console.log('Using the web3 object of the window...') 29 | web3 = new Web3(window.web3.currentProvider) 30 | } 31 | 32 | export default web3 33 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 4 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './app'; 5 | 6 | ReactDOM.render(, document.getElementById('root')); 7 | 8 | -------------------------------------------------------------------------------- /src/interact.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Container from 'react-bootstrap/Container' 3 | import Form from 'react-bootstrap/Form' 4 | import Button from 'react-bootstrap/Button' 5 | import Badge from 'react-bootstrap/Badge' 6 | import ButtonGroup from 'react-bootstrap/ButtonGroup' 7 | import Card from 'react-bootstrap/Card' 8 | import InputGroup from 'react-bootstrap/InputGroup' 9 | 10 | class Interact extends React.Component { 11 | constructor(props) { 12 | super(props) 13 | this.state = { 14 | escrowAddress: this.props.escrowAddress, 15 | remainingTimeToReclaim: 'Unassigned', 16 | remainingTimeToDepositArbitrationFee: 'Unassigned', 17 | status: 'Unassigned', 18 | arbitrator: 'Unassigned', 19 | value: 'Unassigned' 20 | } 21 | } 22 | 23 | async componentDidUpdate(prevProps) { 24 | if (this.props.escrowAddress !== prevProps.escrowAddress) { 25 | await this.setState({ escrowAddress: this.props.escrowAddress }) 26 | this.updateBadges() 27 | } 28 | } 29 | 30 | onEscrowAddressChange = async e => { 31 | await this.setState({ escrowAddress: e.target.value }) 32 | this.updateBadges() 33 | } 34 | 35 | updateBadges = async () => { 36 | const { escrowAddress, status } = this.state 37 | 38 | try { 39 | await this.setState({ 40 | status: await this.props.statusCallback(escrowAddress) 41 | }) 42 | } catch (e) { 43 | console.error(e) 44 | this.setState({ status: 'ERROR' }) 45 | } 46 | 47 | try { 48 | this.setState({ 49 | arbitrator: await this.props.arbitratorCallback(escrowAddress) 50 | }) 51 | } catch (e) { 52 | console.error(e) 53 | this.setState({ arbitrator: 'ERROR' }) 54 | } 55 | 56 | try { 57 | this.setState({ value: await this.props.valueCallback(escrowAddress) }) 58 | } catch (e) { 59 | console.error(e) 60 | this.setState({ value: 'ERROR' }) 61 | } 62 | 63 | if (Number(status) === 0) 64 | try { 65 | this.setState({ 66 | remainingTimeToReclaim: await this.props.remainingTimeToReclaimCallback( 67 | escrowAddress 68 | ) 69 | }) 70 | } catch (e) { 71 | console.error(e) 72 | this.setState({ status: 'ERROR' }) 73 | } 74 | 75 | if (Number(status) === 1) 76 | try { 77 | this.setState({ 78 | remainingTimeToDepositArbitrationFee: await this.props.remainingTimeToDepositArbitrationFeeCallback( 79 | escrowAddress 80 | ) 81 | }) 82 | } catch (e) { 83 | console.error(e) 84 | this.setState({ status: 'ERROR' }) 85 | } 86 | } 87 | 88 | onReclaimFundsButtonClick = async e => { 89 | e.preventDefault() 90 | const { escrowAddress } = this.state 91 | 92 | let arbitrator = await this.props.arbitratorCallback(escrowAddress) 93 | console.log(arbitrator) 94 | 95 | let arbitrationCost = await this.props.arbitrationCostCallback( 96 | arbitrator, 97 | '' 98 | ) 99 | 100 | await this.props.reclaimFundsCallback(escrowAddress, arbitrationCost) 101 | 102 | this.updateBadges() 103 | } 104 | 105 | onReleaseFundsButtonClick = async e => { 106 | e.preventDefault() 107 | const { escrowAddress } = this.state 108 | 109 | await this.props.releaseFundsCallback(escrowAddress) 110 | this.updateBadges() 111 | } 112 | 113 | onDepositArbitrationFeeFromPayeeButtonClicked = async e => { 114 | e.preventDefault() 115 | const { escrowAddress } = this.state 116 | 117 | let arbitrator = await this.props.arbitratorCallback(escrowAddress) 118 | let arbitrationCost = await this.props.arbitrationCostCallback( 119 | arbitrator, 120 | '' 121 | ) 122 | 123 | await this.props.depositArbitrationFeeForPayeeCallback( 124 | escrowAddress, 125 | arbitrationCost 126 | ) 127 | 128 | this.updateBadges() 129 | } 130 | 131 | onInput = e => { 132 | console.log(e.target.files) 133 | this.setState({ fileInput: e.target.files[0] }) 134 | console.log('file input') 135 | } 136 | 137 | onSubmitButtonClick = async e => { 138 | e.preventDefault() 139 | const { escrowAddress, fileInput } = this.state 140 | console.log('submit clicked') 141 | console.log(fileInput) 142 | 143 | var reader = new FileReader() 144 | reader.readAsArrayBuffer(fileInput) 145 | reader.addEventListener('loadend', async () => { 146 | const buffer = Buffer.from(reader.result) 147 | this.props.submitEvidenceCallback(escrowAddress, buffer) 148 | }) 149 | } 150 | 151 | render() { 152 | const { escrowAddress, fileInput } = this.state 153 | return ( 154 | 155 | 156 | 157 | Interact 158 | 159 | 166 | 167 | 168 | Smart Contract State 169 | 170 | 171 | 172 | Status Code: {this.state.status} 173 | 174 | 175 | Escrow Amount in Weis: {this.state.value} 176 | 177 | 178 | Remaining Time To Reclaim Funds:{' '} 179 | {this.state.remainingTimeToReclaim} 180 | 181 | 182 | Remaining Time To Deposit Arbitration Fee:{' '} 183 | {this.state.remainingTimeToDepositArbitrationFee} 184 | 185 | 186 | Arbitrator: {this.state.arbitrator} 187 | 188 | 189 | 197 | 205 | 213 | 214 | 215 |
216 |
217 | 223 | 229 |
230 |
231 | 238 |
239 |
240 |
241 |
242 |
243 |
244 | ) 245 | } 246 | } 247 | 248 | export default Interact 249 | -------------------------------------------------------------------------------- /src/ipfs-publish.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Send file to IPFS network via the Kleros IPFS node 3 | * @param {string} fileName - The name that will be used to store the file. This is useful to preserve extension type. 4 | * @param {ArrayBuffer} data - The raw data from the file to upload. 5 | * @return {object} ipfs response. Should include the hash and path of the stored item. 6 | */ 7 | const ipfsPublish = async (fileName, data) => { 8 | const buffer = await Buffer.from(data) 9 | 10 | return new Promise((resolve, reject) => { 11 | fetch('https://ipfs.kleros.io/add', { 12 | method: 'POST', 13 | body: JSON.stringify({ 14 | fileName, 15 | buffer 16 | }), 17 | headers: { 18 | 'content-type': 'application/json' 19 | } 20 | }) 21 | .then(response => response.json()) 22 | .then(success => resolve(success.data)) 23 | .catch(err => reject(err)) 24 | }) 25 | } 26 | 27 | export default ipfsPublish 28 | --------------------------------------------------------------------------------