From 0d99c1e27077119a7eaf649b1e82279f91dca82c Mon Sep 17 00:00:00 2001 From: Eduardo Burgos Date: Sun, 8 Apr 2018 20:48:54 -0400 Subject: [PATCH 1/7] Add MultipleArbitrableTransaction with the same functionality as ArbitrableTransaction. Implement withdraw function, remove pay function according to #27 --- .../MultipleArbitrableTransaction.sol | 330 ++++++++++++++++++ test/multipleArbitrableTransaction.js | 319 +++++++++++++++++ 2 files changed, 649 insertions(+) create mode 100644 contracts/standard/arbitration/MultipleArbitrableTransaction.sol create mode 100644 test/multipleArbitrableTransaction.js diff --git a/contracts/standard/arbitration/MultipleArbitrableTransaction.sol b/contracts/standard/arbitration/MultipleArbitrableTransaction.sol new file mode 100644 index 00000000..08a0f011 --- /dev/null +++ b/contracts/standard/arbitration/MultipleArbitrableTransaction.sol @@ -0,0 +1,330 @@ +/** + * @title Multiple Arbitrable Transaction + * Bug Bounties: This code hasn't undertaken a bug bounty program yet. + */ + + +pragma solidity ^0.4.15; +import "./Arbitrator.sol"; + +/** @title Multiple Arbitrable Transaction + * This is a a contract for multiple arbitrated transactions which can be reversed by an arbitrator. + * This can be used for buying goods, services and for paying freelancers. + * Parties are identified as "seller" and "buyer". + */ + contract MultipleArbitrableTransaction { + string constant RULING_OPTIONS = "Reimburse buyer;Pay seller"; + + struct Transaction { + address seller; + address buyer; + uint256 amount; + uint256 timeout; // Time in seconds a party can take before being considered unresponding and lose the dispute. + uint disputeId; + bool disputed; + Arbitrator arbitrator; + bytes arbitratorExtraData; + uint sellerFee; // Total fees paid by the seller. + uint buyerFee; // Total fees paid by the buyer. + uint lastInteraction; // Last interaction for the dispute procedure. + Status status; + } + + Transaction[] transactions; + + mapping (uint => uint) disputeTxMap; + + /** @dev Constructor. + */ + function MultipleArbitrableTransaction() public { + } + + /** @dev To be raised when a dispute is created. The main purpose of this event is to let the arbitrator know the meaning ruling IDs. + * @param _transactionId The index of the transaction in dispute + * @param _arbitrator The arbitrator of the contract. + * @param _disputeID ID of the dispute in the Arbitrator contract. + * @param _rulingOptions Map ruling IDs to short description of the ruling in a CSV format using ";" as a delimiter. Note that ruling IDs start a 1. For example "Send funds to buyer;Send funds to seller", means that ruling 1 will make the contract send funds to the buyer and 2 to the seller. + */ + event Dispute(uint indexed _transactionId, Arbitrator indexed _arbitrator, uint indexed _disputeID, string _rulingOptions); + + /** @dev To be raised when a ruling is given. + * @param _transactionId The index of the transaction in dispute + * @param _arbitrator The arbitrator giving the ruling. + * @param _disputeID ID of the dispute in the Arbitrator contract. + * @param _ruling The ruling which was given. + */ + event Ruling(uint indexed _transactionId, Arbitrator indexed _arbitrator, uint indexed _disputeID, uint _ruling); + + /** @dev To be raised when evidence are submitted. Should point to the ressource (evidences are not to be stored on chain due tp gas considerations). + * @param _transactionId The index of the transaction + * @param _arbitrator The arbitrator of the contract. + * @param _disputeID ID of the dispute in the Arbitrator contract. + * @param _party The address of the party submiting the evidence. Note that 0 is kept for evidences not submitted by any party. + * @param _evidence A link to evidence or if it is short the evidence itself. Can be web link ("http://X"), IPFS ("ipfs:/X") or another storing service (using the URI, see https://en.wikipedia.org/wiki/Uniform_Resource_Identifier ). One usecase of short evidence is to include the hash of the plain English contract. + */ + event Evidence(uint indexed _transactionId, Arbitrator indexed _arbitrator, uint indexed _disputeID, address _party, string _evidence); + + /** @dev To be emmited at contract creation. Contains the hash of the plain text contract. This will allow any party to show what was the original contract. + * This event is used as cheap way of storing it. + * @param _transactionId The index of the transaction + * @param _contractHash Keccak256 hash of the plain text contract. + */ + event ContractHash(uint indexed _transactionId, bytes32 _contractHash); + + /** @dev Give a ruling for a dispute. Must be call by the arbitrator. + * The purpose of this function is to ensure that the address calling it has the right to rule on the contract. + * @param _disputeID ID of the dispute in the Arbitrator contract. + * @param _ruling Ruling given by the arbitrator. Note that 0 is reserved for "Not able/wanting to make a decision". + */ + function rule(uint _disputeID, uint _ruling) public { /* onlyArbitrator */ + uint transactionId = disputeTxMap[_disputeID]; + Transaction storage transaction = transactions[transactionId]; + require(msg.sender==address(transaction.arbitrator)); //onlyArbitrator + + Ruling(transactionId, Arbitrator(msg.sender),_disputeID,_ruling); + + executeRuling(_disputeID,_ruling); + } + + enum Status {NoDispute, WaitingSeller, WaitingBuyer, DisputeCreated, Resolved} + + uint8 constant AMOUNT_OF_CHOICES = 2; + uint8 constant BUYER_WINS = 1; + uint8 constant SELLER_WINS = 2; + + enum Party {Seller, Buyer} + + /** @dev Indicate that a party has to pay a fee or would otherwise be considered as loosing. + * @param _transactionId The index of the transaction + * @param _party The party who has to pay. + */ + event HasToPayFee(uint indexed _transactionId, Party _party); + + + /** @dev Pay the arbitration fee to raise a dispute. To be called by the seller. UNTRUSTED. + * Note that the arbitrator can have createDispute throw, which will make this function throw and therefore lead to a party being timed-out. + * This is not a vulnerability as the arbitrator can rule in favor of one party anyway. + * @param _transactionId The index of the transaction + */ + function payArbitrationFeeBySeller(uint _transactionId) payable { + Transaction storage transaction = transactions[_transactionId]; + //onlySeller + require(msg.sender == transaction.seller); + + + uint arbitrationCost = transaction.arbitrator.arbitrationCost(transaction.arbitratorExtraData); + transaction.sellerFee += msg.value; + require(transaction.sellerFee == arbitrationCost); // Require that the total pay at least the arbitration cost. + require(transaction.status < Status.DisputeCreated); // Make sure a dispute has not been created yet. + + transaction.lastInteraction = now; + if (transaction.buyerFee < arbitrationCost) { // The partyB still has to pay. This can also happens if he has paid, but arbitrationCost has increased. + transaction.status = Status.WaitingBuyer; + HasToPayFee(_transactionId, Party.Buyer); + } else { // The partyB has also paid the fee. We create the dispute + raiseDispute(_transactionId, arbitrationCost); + } + } + + + /** @dev Pay the arbitration fee to raise a dispute. To be called by the buyer. UNTRUSTED. + * Note that this function mirror payArbitrationFeeBySeller. + * @param _transactionId The index of the transaction + */ + function payArbitrationFeeByBuyer(uint _transactionId) payable { + Transaction storage transaction = transactions[_transactionId]; + //onlyBuyer + require(msg.sender == transaction.buyer); + + uint arbitrationCost = transaction.arbitrator.arbitrationCost(transaction.arbitratorExtraData); + transaction.buyerFee += msg.value; + require(transaction.buyerFee == arbitrationCost); // Require that the total pay at least the arbitration cost. + require(transaction.status < Status.DisputeCreated); // Make sure a dispute has not been created yet. + + transaction.lastInteraction = now; + if (transaction.sellerFee < arbitrationCost) { // The partyA still has to pay. This can also happens if he has paid, but arbitrationCost has increased. + transaction.status = Status.WaitingSeller; + HasToPayFee(_transactionId, Party.Seller); + } else { // The partyA has also paid the fee. We create the dispute + raiseDispute(_transactionId, arbitrationCost); + } + } + /** @dev Create a dispute. UNTRUSTED. + * @param _transactionId The index of the transaction + * @param _arbitrationCost Amount to pay the arbitrator. + */ + function raiseDispute(uint _transactionId, uint _arbitrationCost) internal { + Transaction storage transaction = transactions[_transactionId]; + transaction.status = Status.DisputeCreated; + transaction.disputeId = transaction.arbitrator.createDispute.value(_arbitrationCost)(AMOUNT_OF_CHOICES,transaction.arbitratorExtraData); + transaction.disputed = true; + disputeTxMap[transaction.disputeId] = _transactionId; + Dispute(_transactionId, transaction.arbitrator, transaction.disputeId,RULING_OPTIONS); + } + + /** @dev Reimburse partyA if partyB fails to pay the fee. + * @param _transactionId The index of the transaction + */ + function timeOutBySeller(uint _transactionId) { + Transaction storage transaction = transactions[_transactionId]; + //onlyPartyA + require(msg.sender == transaction.seller); + + + require(transaction.status==Status.WaitingBuyer); + require(now>=transaction.lastInteraction+transaction.timeout); + + executeRuling(transaction.disputeId, SELLER_WINS); + } + + /** @dev Pay partyB if partyA fails to pay the fee. + * @param _transactionId The index of the transaction + */ + function timeOutByBuyer(uint _transactionId) { + Transaction storage transaction = transactions[_transactionId]; + //onlyPartyB + require(msg.sender == transaction.buyer); + + + require(transaction.status == Status.WaitingSeller); + require(now>=transaction.lastInteraction+transaction.timeout); + + executeRuling(transaction.disputeId,BUYER_WINS); + } + + /** @dev Submit a reference to evidence. EVENT. + * @param _transactionId The index of the transaction + * @param _evidence A link to an evidence using its URI. + */ + function submitEvidence(uint _transactionId, string _evidence) { + Transaction storage transaction = transactions[_transactionId]; + //onlyParty + require(msg.sender == transaction.buyer || msg.sender == transaction.seller); + + require(transaction.status>=Status.DisputeCreated); + Evidence(_transactionId, transaction.arbitrator,transaction.disputeId,msg.sender,_evidence); + } + + /** @dev Appeal an appealable ruling. + * Transfer the funds to the arbitrator. + * Note that no checks are required as the checks are done by the arbitrator. + * @param _transactionId The index of the transaction + * @param _extraData Extra data for the arbitrator appeal procedure. + */ + function appeal(uint _transactionId, bytes _extraData) payable public { + Transaction storage transaction = transactions[_transactionId]; + //onlyParty + require(msg.sender == transaction.buyer || msg.sender == transaction.seller); + + transaction.arbitrator.appeal.value(msg.value)(transaction.disputeId,_extraData); + } + + + + /** @dev + * @param _arbitrator The arbitrator of the contract. + * @param _hashContract Keccak hash of the plain English contract. + * @param _timeout Time after which a party automatically loose a dispute. + * @param _seller The recipient of the transaction. + * @param _arbitratorExtraData Extra data for the arbitrator. + */ + function createTransaction(Arbitrator _arbitrator, bytes32 _hashContract, uint _timeout, address _seller, bytes _arbitratorExtraData) payable public returns (uint transactionIndex) { + transactions.push(Transaction({ + seller: _seller, + buyer: msg.sender, + amount: msg.value, + timeout: _timeout, + disputed: false, + arbitrator: _arbitrator, + arbitratorExtraData: _arbitratorExtraData, + disputeId: 0, + // Total fees paid by the partyA. + sellerFee: 0, + // Total fees paid by the partyB. + buyerFee: 0, + // Last interaction for the dispute procedure. + lastInteraction: now, + status: Status.NoDispute + })); + ContractHash(transactions.length - 1, _hashContract); + return transactions.length - 1; + } + + /** @dev + * returns the last transaction id whose buyer is the sender + */ + function lastTransactionId () constant public returns (uint index) { + uint len = transactions.length; + for (uint i = len - 1; i>=0; i--) { + // do something + if (transactions[i].buyer == msg.sender) { + return i; + } + } + } + + /** @dev + * returns the transaction amount. Can be called by buyer, seller or arbitrator. + * @param _transactionId The index of the transaction + */ + function amount (uint _transactionId) constant public returns (uint amount) { + Transaction storage transaction = transactions[_transactionId]; + //onlyParty or arbitrator + require(msg.sender == transaction.buyer || msg.sender == transaction.seller || msg.sender == address(transaction.arbitrator)); + return transaction.amount; + } + + /** @dev + * transfer the transaction's amount to the seller if the timeout has passed + * @param _transactionId The index of the transaction + */ + function withdraw(uint _transactionId) public { + Transaction storage transaction = transactions[_transactionId]; + //onlySeller ifPastTimeout + require(msg.sender == transaction.seller); + require(now>=transaction.lastInteraction+transaction.timeout); + + transaction.seller.send(transaction.amount); + transaction.amount = 0; + + transaction.status = Status.Resolved; + } + + /** @dev Reimburse party A. To be called if the good or service can't be fully provided. + * @param _transactionId The index of the transaction + * @param _amountReimbursed Amount to reimburse in wei. + */ + function reimburse(uint _transactionId, uint _amountReimbursed) public { + Transaction storage transaction = transactions[_transactionId]; + require(transaction.seller == msg.sender); //onlySeller + require(_amountReimbursed <= transaction.amount); + transaction.buyer.transfer(_amountReimbursed); + transaction.amount -= _amountReimbursed; + } + + /** @dev Execute a ruling of a dispute. It reimburse the fee to the winning party. + * This need to be extended by contract inheriting from it. + * @param _disputeID ID of the dispute in the Arbitrator contract. + * @param _ruling Ruling given by the arbitrator. 1 : Reimburse the partyA. 2 : Pay the partyB. + */ + function executeRuling(uint _disputeID, uint _ruling) internal { + uint transactionId = disputeTxMap[_disputeID]; + Transaction storage transaction = transactions[transactionId]; + + require(_disputeID == transaction.disputeId); + require(_ruling <= AMOUNT_OF_CHOICES); + + // Give the arbitration fee back. + // Note that we use send to prevent a party from blocking the execution. + if (_ruling == SELLER_WINS) { + transaction.seller.send(transaction.sellerFee > transaction.buyerFee ? transaction.sellerFee : transaction.buyerFee); // In both cases sends the highest amount paid to avoid ETH to be stuck in the contract if the arbitrator lowers its fee. + transaction.seller.send(transaction.amount); + } else if (_ruling == BUYER_WINS) { + transaction.buyer.send(transaction.sellerFee > transaction.buyerFee ? transaction.sellerFee : transaction.buyerFee); + transaction.buyer.send(transaction.amount); + } + + transaction.amount = 0; + transaction.status = Status.Resolved; + } +} \ No newline at end of file diff --git a/test/multipleArbitrableTransaction.js b/test/multipleArbitrableTransaction.js new file mode 100644 index 00000000..60bc2142 --- /dev/null +++ b/test/multipleArbitrableTransaction.js @@ -0,0 +1,319 @@ +/* eslint-disable no-undef */ // Avoid the linter considering truffle elements as undef. +const { expectThrow, increaseTime } = require('../helpers/utils') +const MultipleArbitrableTransaction = artifacts.require('./MultipleArbitrableTransaction.sol') +const CentralizedArbitrator = artifacts.require('./CentralizedArbitrator.sol') + +contract('MultipleArbitrableTransaction', function (accounts) { + let payer = accounts[0] + let payee = accounts[1] + let arbitrator = accounts[2] + let other = accounts[3] + let amount = 1000 + let timeout = 100 + let arbitrationFee = 20 + let gasPrice = 5000000000 + let contractHash = 0x6aa0bb2779ab006be0739900654a89f1f8a2d7373ed38490a7cbab9c9392e1ff + + it('Should handle 1 transaction', async () => { + let multipleContract = await MultipleArbitrableTransaction.new({ from: payer }) + await multipleContract.createTransaction(0x0, contractHash, timeout, payee, 0x0, {from: payer, value: amount}) + let arbitrableTransactionId = (await multipleContract.lastTransactionId.call(undefined, { from: payer })).toNumber() + let payerBalanceBeforeReimbursment = web3.eth.getBalance(payer) + await multipleContract.reimburse(arbitrableTransactionId, 1000, {from: payee}) + let newPayerBalance = web3.eth.getBalance(payer) + let newContractBalance = web3.eth.getBalance(multipleContract.address) + let newAmount = await multipleContract.amount.call(arbitrableTransactionId, { from: payer }) + + assert.equal(newPayerBalance.toString(), payerBalanceBeforeReimbursment.plus(1000).toString(), 'The payer has not been reimbursed correctly') + assert.equal(newContractBalance.toNumber(), 0, 'Bad amount in the contract') + assert.equal(newAmount.toNumber(), 0, 'Amount not updated correctly') + }) + + it('Should handle 3 transaction', async () => { + let multipleContract = await MultipleArbitrableTransaction.new({ from: payer }) + for (var cnt = 0; cnt < 3; cnt += 1) { + await multipleContract.createTransaction(0x0, contractHash, timeout, payee, 0x0, {from: payer, value: amount}) + let arbitrableTransactionId = (await multipleContract.lastTransactionId.call(undefined, { from: payer })).toNumber() + let payerBalanceBeforeReimbursment = web3.eth.getBalance(payer) + await multipleContract.reimburse(arbitrableTransactionId, 1000, {from: payee}) + let newPayerBalance = web3.eth.getBalance(payer) + let newContractBalance = web3.eth.getBalance(multipleContract.address) + let newAmount = await multipleContract.amount.call(arbitrableTransactionId, { from: payer }) + + assert.equal(newPayerBalance.toString(), payerBalanceBeforeReimbursment.plus(1000).toString(), 'The payer has not been reimbursed correctly') + assert.equal(newContractBalance.toNumber(), 0, 'Bad amount in the contract') + assert.equal(newAmount.toNumber(), 0, 'Amount not updated correctly') + } + }) + + it('Should put 1000 wei in the contract', async () => { + let multipleContract = await MultipleArbitrableTransaction.new({ from: payer }) + await multipleContract.createTransaction(0x0, contractHash, timeout, payee, 0x0, {from: payer, value: amount}) + let arbitrableTransactionId = (await multipleContract.lastTransactionId.call(undefined, { from: payer })).toNumber() + + assert.equal(web3.eth.getBalance(multipleContract.address), 1000, "The contract hasn't received the wei correctly.") + + let amountSending = await multipleContract.amount(arbitrableTransactionId) + assert.equal(amountSending.toNumber(), 1000, "The contract hasn't updated its amount correctly.") + }) + + // Pay + it('The payee should withdraw', async () => { + let initialPayeeBalance = web3.eth.getBalance(payee) + let multipleContract = await MultipleArbitrableTransaction.new({ from: payer }) + //The timeout is set to 0 to be able to withdraw right away in the test + await multipleContract.createTransaction(0x0, contractHash, timeout, payee, 0x0, {from: payer, value: amount}) + increaseTime(timeout + 1) + let arbitrableTransactionId = (await multipleContract.lastTransactionId.call(undefined, { from: payer })).toNumber() + let tx = await multipleContract.withdraw(arbitrableTransactionId, {from: payee}) + let consumed = tx.receipt.gasUsed * 100000000000 + let newPayeeBalance = web3.eth.getBalance(payee) + assert.equal(newPayeeBalance.toString(), initialPayeeBalance.plus(1000 - consumed).toString(), "The payee hasn't been paid properly") + }) + + it('The payer should not withdraw', async () => { + let multipleContract = await MultipleArbitrableTransaction.new({ from: payer }) + //The timeout is set to 0 to be able to withdraw right away in the test + await multipleContract.createTransaction(0x0, contractHash, timeout, payee, 0x0, {from: payer, value: amount}) + let arbitrableTransactionId = (await multipleContract.lastTransactionId.call(undefined, { from: payer })).toNumber() + await expectThrow(multipleContract.withdraw(arbitrableTransactionId, {from: payer})) + }) + + // Reimburse + it('Should reimburse 507 to the payer', async () => { + let multipleContract = await MultipleArbitrableTransaction.new({ from: payer }) + //The timeout is set to 0 to be able to withdraw right away in the test + await multipleContract.createTransaction(0x0, contractHash, 0 /* timeout */, payee, 0x0, {from: payer, value: amount}) + let arbitrableTransactionId = (await multipleContract.lastTransactionId.call(undefined, { from: payer })).toNumber() + + let payerBalanceBeforeReimbursment = web3.eth.getBalance(payer) + await multipleContract.reimburse(arbitrableTransactionId, 507, {from: payee}) + let newPayerBalance = web3.eth.getBalance(payer) + let newContractBalance = web3.eth.getBalance(multipleContract.address) + let newAmount = await multipleContract.amount(arbitrableTransactionId) + + assert.equal(newPayerBalance.toString(), payerBalanceBeforeReimbursment.plus(507).toString(), 'The payer has not been reimbursed correctly') + assert.equal(newContractBalance.toNumber(), 493, 'Bad amount in the contract') + assert.equal(newAmount.toNumber(), 493, 'Amount not updated correctly') + }) + + it('Should reimburse 1000 (all) to the payer', async () => { + let multipleContract = await MultipleArbitrableTransaction.new({ from: payer }) + + await multipleContract.createTransaction(0x0, contractHash, timeout, payee, 0x0, {from: payer, value: amount}) + let arbitrableTransactionId = (await multipleContract.lastTransactionId.call(undefined, { from: payer })).toNumber() + + let payerBalanceBeforeReimbursment = web3.eth.getBalance(payer) + await multipleContract.reimburse(arbitrableTransactionId, 1000, {from: payee}) + let newPayerBalance = web3.eth.getBalance(payer) + let newContractBalance = web3.eth.getBalance(multipleContract.address) + let newAmount = await multipleContract.amount(arbitrableTransactionId, { from: payer }) + + assert.equal(newPayerBalance.toString(), payerBalanceBeforeReimbursment.plus(1000).toString(), 'The payer has not been reimbursed correctly') + assert.equal(newContractBalance.toNumber(), 0, 'Bad amount in the contract') + assert.equal(newAmount.toNumber(), 0, 'Amount not updated correctly') + }) + + it('Should fail if we try to reimburse more', async () => { + let multipleContract = await MultipleArbitrableTransaction.new({ from: payer }) + + await multipleContract.createTransaction(0x0, contractHash, timeout, payee, 0x0, {from: payer, value: amount}) + let arbitrableTransactionId = (await multipleContract.lastTransactionId.call(undefined, { from: payer })).toNumber() + await expectThrow(multipleContract.reimburse(arbitrableTransactionId, 1003, {from: payee})) + }) + + it('Should fail if the payer to it', async () => { + let multipleContract = await MultipleArbitrableTransaction.new({ from: payer }) + + await multipleContract.createTransaction(0x0, contractHash, timeout, payee, 0x0, {from: payer, value: amount}) + let arbitrableTransactionId = (await multipleContract.lastTransactionId.call(undefined, { from: payer })).toNumber() + await expectThrow(multipleContract.reimburse(arbitrableTransactionId, 1000, {from: payer})) + }) + + // executeRuling + it('Should reimburse the payer (including arbitration fee) when the arbitrator decides so', async () => { + let centralizedArbitrator = await CentralizedArbitrator.new(arbitrationFee, {from: arbitrator}) + + let multipleContract = await MultipleArbitrableTransaction.new({ from: payer }) + + await multipleContract.createTransaction(centralizedArbitrator.address, contractHash, timeout, payee, 0x0, {from: payer, value: amount}) + let arbitrableTransactionId = (await multipleContract.lastTransactionId.call(undefined, { from: payer })).toNumber() + + await multipleContract.payArbitrationFeeBySeller(arbitrableTransactionId, {from: payee, value: arbitrationFee}) + await multipleContract.payArbitrationFeeByBuyer(arbitrableTransactionId, {from: payer, value: arbitrationFee}) + let payerBalanceBeforeReimbursment = web3.eth.getBalance(payer) + await centralizedArbitrator.giveRuling(0, 1, {from: arbitrator}) + let newPayerBalance = web3.eth.getBalance(payer) + assert.equal(newPayerBalance.toString(), payerBalanceBeforeReimbursment.plus(1020).toString(), 'The payer has not been reimbursed correctly') + }) + + it('Should pay the payee and reimburse him the arbitration fee when the arbitrator decides so', async () => { + let centralizedArbitrator = await CentralizedArbitrator.new(arbitrationFee, {from: arbitrator}) + let multipleContract = await MultipleArbitrableTransaction.new({ from: payer }) + + await multipleContract.createTransaction(centralizedArbitrator.address, contractHash, timeout, payee, 0x0, {from: payer, value: amount}) + let arbitrableTransactionId = (await multipleContract.lastTransactionId.call(undefined, { from: payer })).toNumber() + + await multipleContract.payArbitrationFeeByBuyer(arbitrableTransactionId, {from: payer, value: arbitrationFee}) + await multipleContract.payArbitrationFeeBySeller(arbitrableTransactionId, {from: payee, value: arbitrationFee}) + let payeeBalanceBeforePay = web3.eth.getBalance(payee) + await centralizedArbitrator.giveRuling(0, 2, {from: arbitrator}) + let newPayeeBalance = web3.eth.getBalance(payee) + assert.equal(newPayeeBalance.toString(), payeeBalanceBeforePay.plus(1020).toString(), 'The payee has not been paid properly') + }) + + it('It should do nothing if the arbitrator decides so', async () => { + let centralizedArbitrator = await CentralizedArbitrator.new(arbitrationFee, {from: arbitrator}) + let multipleContract = await MultipleArbitrableTransaction.new({ from: payer }) + + await multipleContract.createTransaction(centralizedArbitrator.address, contractHash, timeout, payee, 0x0, {from: payer, value: amount}) + let arbitrableTransactionId = (await multipleContract.lastTransactionId.call(undefined, { from: payer })).toNumber() + await multipleContract.payArbitrationFeeByBuyer(arbitrableTransactionId, {from: payer, value: arbitrationFee}) + await multipleContract.payArbitrationFeeBySeller(arbitrableTransactionId, {from: payee, value: arbitrationFee}) + let payeeBalanceBeforePay = web3.eth.getBalance(payee) + let payerBalanceBeforeReimbursment = web3.eth.getBalance(payer) + await centralizedArbitrator.giveRuling(0, 0, {from: arbitrator}) + let newPayeeBalance = web3.eth.getBalance(payee) + let newPayerBalance = web3.eth.getBalance(payer) + assert.equal(newPayeeBalance.toString(), payeeBalanceBeforePay.toString(), "The payee got wei while it shouldn't") + assert.equal(newPayerBalance.toString(), payerBalanceBeforeReimbursment.toString(), "The payer got wei while it shouldn't") + }) + + it('Should reimburse the payer in case of timeout of the payee', async () => { + let centralizedArbitrator = await CentralizedArbitrator.new(arbitrationFee, {from: arbitrator}) + + let multipleContract = await MultipleArbitrableTransaction.new({ from: payer }) + + await multipleContract.createTransaction(centralizedArbitrator.address, contractHash, timeout, payee, 0x0, {from: payer, value: amount}) + let arbitrableTransactionId = (await multipleContract.lastTransactionId.call(undefined, { from: payer })).toNumber() + await multipleContract.payArbitrationFeeByBuyer(arbitrableTransactionId, {from: payer, value: arbitrationFee}) + increaseTime(timeout + 1) + let payerBalanceBeforeReimbursment = web3.eth.getBalance(payer) + let tx = await multipleContract.timeOutByBuyer(arbitrableTransactionId, {from: payer, gasPrice: gasPrice}) + let txFee = tx.receipt.gasUsed * gasPrice + let newPayerBalance = web3.eth.getBalance(payer) + assert.equal(newPayerBalance.toString(), payerBalanceBeforeReimbursment.plus(1020).minus(txFee).toString(), 'The payer has not been reimbursed correctly') + }) + + it("Shouldn't work before timeout for the payer", async () => { + let centralizedArbitrator = await CentralizedArbitrator.new(arbitrationFee, {from: arbitrator}) + + let multipleContract = await MultipleArbitrableTransaction.new({ from: payer }) + + await multipleContract.createTransaction(centralizedArbitrator.address, contractHash, timeout, payee, 0x0, {from: payer, value: amount}) + let arbitrableTransactionId = (await multipleContract.lastTransactionId.call(undefined, { from: payer })).toNumber() + await expectThrow(multipleContract.timeOutByBuyer(arbitrableTransactionId, {from: payer, gasPrice: gasPrice})) + await multipleContract.payArbitrationFeeByBuyer(arbitrableTransactionId, {from: payer, value: arbitrationFee}) + increaseTime(1) + await expectThrow(multipleContract.timeOutByBuyer(arbitrableTransactionId, {from: payer, gasPrice: gasPrice})) + }) + + it('Should pay and reimburse the payee in case of timeout of the payer', async () => { + let centralizedArbitrator = await CentralizedArbitrator.new(arbitrationFee, {from: arbitrator}) + + let multipleContract = await MultipleArbitrableTransaction.new({ from: payer }) + + await multipleContract.createTransaction(centralizedArbitrator.address, contractHash, timeout, payee, 0x0, {from: payer, value: amount}) + let arbitrableTransactionId = (await multipleContract.lastTransactionId.call(undefined, { from: payer })).toNumber() + + await multipleContract.payArbitrationFeeBySeller(arbitrableTransactionId, {from: payee, value: arbitrationFee}) + increaseTime(timeout + 1) + let payeeBalanceBeforeReimbursment = web3.eth.getBalance(payee) + let tx = await multipleContract.timeOutBySeller(arbitrableTransactionId, {from: payee, gasPrice: gasPrice}) + let txFee = tx.receipt.gasUsed * gasPrice + let newPayeeBalance = web3.eth.getBalance(payee) + assert.equal(newPayeeBalance.toString(), payeeBalanceBeforeReimbursment.plus(1020).minus(txFee).toString(), 'The payee has not been paid correctly') + }) + + it("Shouldn't work before timeout for the payee", async () => { + let centralizedArbitrator = await CentralizedArbitrator.new(arbitrationFee, {from: arbitrator}) + + let multipleContract = await MultipleArbitrableTransaction.new({ from: payer }) + + await multipleContract.createTransaction(centralizedArbitrator.address, contractHash, timeout, payee, 0x0, {from: payer, value: amount}) + let arbitrableTransactionId = (await multipleContract.lastTransactionId.call(undefined, { from: payer })).toNumber() + + await expectThrow(multipleContract.timeOutBySeller(arbitrableTransactionId, {from: payee, gasPrice: gasPrice})) + await multipleContract.payArbitrationFeeBySeller(arbitrableTransactionId, {from: payee, value: arbitrationFee}) + increaseTime(1) + await expectThrow(multipleContract.timeOutBySeller(arbitrableTransactionId, {from: payee, gasPrice: gasPrice})) + }) + + // submitEvidence + it('Should create events when evidence is submitted by the payer', async () => { + let centralizedArbitrator = await CentralizedArbitrator.new(arbitrationFee, {from: arbitrator}) + + let multipleContract = await MultipleArbitrableTransaction.new({ from: payer }) + + await multipleContract.createTransaction(centralizedArbitrator.address, contractHash, timeout, payee, 0x0, {from: payer, value: amount}) + let arbitrableTransactionId = (await multipleContract.lastTransactionId.call(undefined, { from: payer })).toNumber() + await multipleContract.payArbitrationFeeByBuyer(arbitrableTransactionId, {from: payer, value: arbitrationFee}) + await multipleContract.payArbitrationFeeBySeller(arbitrableTransactionId, {from: payee, value: arbitrationFee}) + let tx = await multipleContract.submitEvidence(arbitrableTransactionId, 'ipfs:/X', {from: payer}) + assert.equal(tx.logs[0].event, 'Evidence') + assert.equal(tx.logs[0].args._arbitrator, centralizedArbitrator.address) + assert.equal(tx.logs[0].args._party, payer) + assert.equal(tx.logs[0].args._evidence, 'ipfs:/X') + }) + + it('Should create events when evidence is submitted by the payee', async () => { + let centralizedArbitrator = await CentralizedArbitrator.new(arbitrationFee, {from: arbitrator}) + + let multipleContract = await MultipleArbitrableTransaction.new({ from: payer }) + + await multipleContract.createTransaction(centralizedArbitrator.address, contractHash, timeout, payee, 0x0, {from: payer, value: amount}) + let arbitrableTransactionId = (await multipleContract.lastTransactionId.call(undefined, { from: payer })).toNumber() + await multipleContract.payArbitrationFeeByBuyer(arbitrableTransactionId, {from: payer, value: arbitrationFee}) + await multipleContract.payArbitrationFeeBySeller(arbitrableTransactionId, {from: payee, value: arbitrationFee}) + let tx = await multipleContract.submitEvidence(arbitrableTransactionId, 'ipfs:/X', {from: payee}) + assert.equal(tx.logs[0].event, 'Evidence') + assert.equal(tx.logs[0].args._arbitrator, centralizedArbitrator.address) + assert.equal(tx.logs[0].args._party, payee) + assert.equal(tx.logs[0].args._evidence, 'ipfs:/X') + }) + + it('Should fail if someone else try to submit', async () => { + let centralizedArbitrator = await CentralizedArbitrator.new(arbitrationFee, {from: arbitrator}) + + let multipleContract = await MultipleArbitrableTransaction.new({ from: payer }) + + await multipleContract.createTransaction(centralizedArbitrator.address, contractHash, timeout, payee, 0x0, {from: payer, value: amount}) + let arbitrableTransactionId = (await multipleContract.lastTransactionId.call(undefined, { from: payer })).toNumber() + + await multipleContract.payArbitrationFeeByBuyer(arbitrableTransactionId, {from: payer, value: arbitrationFee}) + await multipleContract.payArbitrationFeeBySeller(arbitrableTransactionId, {from: payee, value: arbitrationFee}) + await expectThrow(multipleContract.submitEvidence(arbitrableTransactionId, 'ipfs:/X', {from: other})) + }) + + + it('Should handle multiple transactions concurrently', async () => { + let centralizedArbitrator = await CentralizedArbitrator.new(arbitrationFee, {from: arbitrator}) + + let multipleContract = await MultipleArbitrableTransaction.new({ from: payer }) + + await multipleContract.createTransaction(centralizedArbitrator.address, contractHash, timeout, payee, 0x0, {from: payer, value: amount}) + let arbitrableTransactionId1 = (await multipleContract.lastTransactionId.call(undefined, { from: payer })).toNumber() + + await multipleContract.createTransaction(centralizedArbitrator.address, contractHash, timeout, payee, 0x0, {from: payer, value: amount}) + let arbitrableTransactionId2 = (await multipleContract.lastTransactionId.call(undefined, { from: payer })).toNumber() + + await multipleContract.payArbitrationFeeByBuyer(arbitrableTransactionId2, {from: payer, value: arbitrationFee}) + await multipleContract.payArbitrationFeeBySeller(arbitrableTransactionId1, {from: payee, value: arbitrationFee}) + //This generates transaction 1 dispute 0 + await multipleContract.payArbitrationFeeByBuyer(arbitrableTransactionId1, {from: payer, value: arbitrationFee}) + //This generates transaction 2 dispute 1 + await multipleContract.payArbitrationFeeBySeller(arbitrableTransactionId2, {from: payee, value: arbitrationFee}) + + let payerBalanceBeforeReimbursment = web3.eth.getBalance(payer) + //Ruling for transaction 1 + await centralizedArbitrator.giveRuling(0, 1, {from: arbitrator}) + let newPayerBalance = web3.eth.getBalance(payer) + assert.equal(newPayerBalance.toString(), payerBalanceBeforeReimbursment.plus(1020).toString(), 'The payer has not been reimbursed correctly') + + let payeeBalanceBeforePay = web3.eth.getBalance(payee) + //ruling for transaction 2 + await centralizedArbitrator.giveRuling(1, 2, {from: arbitrator}) + let newPayeeBalance = web3.eth.getBalance(payee) + assert.equal(newPayeeBalance.toString(), payeeBalanceBeforePay.plus(1020).toString(), 'The payee has not been paid properly') + }) +}) From 428f974c98b0bf995da0cd5ddc2719908f0c11a5 Mon Sep 17 00:00:00 2001 From: Eduardo Burgos Date: Tue, 10 Apr 2018 10:34:26 -0400 Subject: [PATCH 2/7] Change dispute => transaction map to have a bytes32 key Changed key to a bytes32 type in order to store a hash of (arbitrator, disputeID) to address the fact that a disputeID can be the same for different arbitrators. Added a test for that scenario. --- .../MultipleArbitrableTransaction.sol | 8 ++--- test/multipleArbitrableTransaction.js | 32 +++++++++++++++++++ 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/contracts/standard/arbitration/MultipleArbitrableTransaction.sol b/contracts/standard/arbitration/MultipleArbitrableTransaction.sol index 08a0f011..a556f498 100644 --- a/contracts/standard/arbitration/MultipleArbitrableTransaction.sol +++ b/contracts/standard/arbitration/MultipleArbitrableTransaction.sol @@ -32,7 +32,7 @@ import "./Arbitrator.sol"; Transaction[] transactions; - mapping (uint => uint) disputeTxMap; + mapping (bytes32 => uint) disputeTxMap; /** @dev Constructor. */ @@ -77,7 +77,7 @@ import "./Arbitrator.sol"; * @param _ruling Ruling given by the arbitrator. Note that 0 is reserved for "Not able/wanting to make a decision". */ function rule(uint _disputeID, uint _ruling) public { /* onlyArbitrator */ - uint transactionId = disputeTxMap[_disputeID]; + uint transactionId = disputeTxMap[keccak256(msg.sender,_disputeID)]; Transaction storage transaction = transactions[transactionId]; require(msg.sender==address(transaction.arbitrator)); //onlyArbitrator @@ -158,7 +158,7 @@ import "./Arbitrator.sol"; transaction.status = Status.DisputeCreated; transaction.disputeId = transaction.arbitrator.createDispute.value(_arbitrationCost)(AMOUNT_OF_CHOICES,transaction.arbitratorExtraData); transaction.disputed = true; - disputeTxMap[transaction.disputeId] = _transactionId; + disputeTxMap[keccak256(transaction.arbitrator, transaction.disputeId)] = _transactionId; Dispute(_transactionId, transaction.arbitrator, transaction.disputeId,RULING_OPTIONS); } @@ -308,7 +308,7 @@ import "./Arbitrator.sol"; * @param _ruling Ruling given by the arbitrator. 1 : Reimburse the partyA. 2 : Pay the partyB. */ function executeRuling(uint _disputeID, uint _ruling) internal { - uint transactionId = disputeTxMap[_disputeID]; + uint transactionId = disputeTxMap[keccak256(msg.sender,_disputeID)]; Transaction storage transaction = transactions[transactionId]; require(_disputeID == transaction.disputeId); diff --git a/test/multipleArbitrableTransaction.js b/test/multipleArbitrableTransaction.js index 60bc2142..c07edb1d 100644 --- a/test/multipleArbitrableTransaction.js +++ b/test/multipleArbitrableTransaction.js @@ -316,4 +316,36 @@ contract('MultipleArbitrableTransaction', function (accounts) { let newPayeeBalance = web3.eth.getBalance(payee) assert.equal(newPayeeBalance.toString(), payeeBalanceBeforePay.plus(1020).toString(), 'The payee has not been paid properly') }) + + it('Should handle multiple transactions and arbitrators concurrently', async () => { + let centralizedArbitrator1 = await CentralizedArbitrator.new(arbitrationFee, {from: arbitrator}) + let centralizedArbitrator2 = await CentralizedArbitrator.new(arbitrationFee, {from: other}) + + let multipleContract = await MultipleArbitrableTransaction.new({ from: payer }) + + await multipleContract.createTransaction(centralizedArbitrator1.address, contractHash, timeout, payee, 0x0, {from: payer, value: amount}) + let arbitrableTransactionId1 = (await multipleContract.lastTransactionId.call(undefined, { from: payer })).toNumber() + + await multipleContract.createTransaction(centralizedArbitrator2.address, contractHash, timeout, payee, 0x0, {from: payer, value: amount}) + let arbitrableTransactionId2 = (await multipleContract.lastTransactionId.call(undefined, { from: payer })).toNumber() + + await multipleContract.payArbitrationFeeByBuyer(arbitrableTransactionId2, {from: payer, value: arbitrationFee}) + await multipleContract.payArbitrationFeeBySeller(arbitrableTransactionId1, {from: payee, value: arbitrationFee}) + //This generates transaction 1 dispute 0 from arbitrator 1 + await multipleContract.payArbitrationFeeByBuyer(arbitrableTransactionId1, {from: payer, value: arbitrationFee}) + //This generates transaction 2 dispute 0 from arbitrator 2 + await multipleContract.payArbitrationFeeBySeller(arbitrableTransactionId2, {from: payee, value: arbitrationFee}) + + let payerBalanceBeforeReimbursment = web3.eth.getBalance(payer) + //Ruling for transaction 1 + await centralizedArbitrator1.giveRuling(0, 1, {from: arbitrator}) + let newPayerBalance = web3.eth.getBalance(payer) + assert.equal(newPayerBalance.toString(), payerBalanceBeforeReimbursment.plus(1020).toString(), 'The payer has not been reimbursed correctly') + + let payeeBalanceBeforePay = web3.eth.getBalance(payee) + //ruling for transaction 2 + await centralizedArbitrator2.giveRuling(0, 2, {from: other}) + let newPayeeBalance = web3.eth.getBalance(payee) + assert.equal(newPayeeBalance.toString(), payeeBalanceBeforePay.plus(1020).toString(), 'The payee has not been paid properly') + }) }) From 91988282a75915de584842af4fbbc8c0641fa989 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Lesaege?= Date: Sat, 23 Jun 2018 20:32:35 +0100 Subject: [PATCH 3/7] Update comments --- .../standard/arbitration/MultipleArbitrableTransaction.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/standard/arbitration/MultipleArbitrableTransaction.sol b/contracts/standard/arbitration/MultipleArbitrableTransaction.sol index a556f498..4cd2a73c 100644 --- a/contracts/standard/arbitration/MultipleArbitrableTransaction.sol +++ b/contracts/standard/arbitration/MultipleArbitrableTransaction.sol @@ -76,10 +76,10 @@ import "./Arbitrator.sol"; * @param _disputeID ID of the dispute in the Arbitrator contract. * @param _ruling Ruling given by the arbitrator. Note that 0 is reserved for "Not able/wanting to make a decision". */ - function rule(uint _disputeID, uint _ruling) public { /* onlyArbitrator */ + function rule(uint _disputeID, uint _ruling) public { uint transactionId = disputeTxMap[keccak256(msg.sender,_disputeID)]; Transaction storage transaction = transactions[transactionId]; - require(msg.sender==address(transaction.arbitrator)); //onlyArbitrator + require(msg.sender==address(transaction.arbitrator)); // Only arbitrator. Ruling(transactionId, Arbitrator(msg.sender),_disputeID,_ruling); @@ -327,4 +327,4 @@ import "./Arbitrator.sol"; transaction.amount = 0; transaction.status = Status.Resolved; } -} \ No newline at end of file +} From 55181eb0bc06023ecea44dfba0fb103483decf10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Lesaege?= Date: Sat, 23 Jun 2018 21:57:22 +0100 Subject: [PATCH 4/7] Allow paying more As the comment stated, parties can pay more. This is required to cope with the case where the arbitrator decreases the fee. --- contracts/standard/arbitration/TwoPartyArbitrable.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/standard/arbitration/TwoPartyArbitrable.sol b/contracts/standard/arbitration/TwoPartyArbitrable.sol index d6fa019b..7893c921 100644 --- a/contracts/standard/arbitration/TwoPartyArbitrable.sol +++ b/contracts/standard/arbitration/TwoPartyArbitrable.sol @@ -63,7 +63,7 @@ contract TwoPartyArbitrable is Arbitrable { function payArbitrationFeeByPartyA() payable onlyPartyA { uint arbitrationCost=arbitrator.arbitrationCost(arbitratorExtraData); partyAFee+=msg.value; - require(partyAFee == arbitrationCost); // Require that the total pay at least the arbitration cost. + require(partyAFee >= arbitrationCost); // Require that the total pay at least the arbitration cost. require(status= arbitrationCost); // Require that the total pay at least the arbitration cost. require(status Date: Sat, 23 Jun 2018 23:08:30 +0100 Subject: [PATCH 5/7] Cosmetic changes --- .../MultipleArbitrableTransaction.sol | 88 ++++++++----------- 1 file changed, 39 insertions(+), 49 deletions(-) diff --git a/contracts/standard/arbitration/MultipleArbitrableTransaction.sol b/contracts/standard/arbitration/MultipleArbitrableTransaction.sol index 4cd2a73c..d47d7eeb 100644 --- a/contracts/standard/arbitration/MultipleArbitrableTransaction.sol +++ b/contracts/standard/arbitration/MultipleArbitrableTransaction.sol @@ -14,6 +14,16 @@ import "./Arbitrator.sol"; */ contract MultipleArbitrableTransaction { string constant RULING_OPTIONS = "Reimburse buyer;Pay seller"; + + + + uint8 constant AMOUNT_OF_CHOICES = 2; + uint8 constant BUYER_WINS = 1; + uint8 constant SELLER_WINS = 2; + + enum Party {Seller, Buyer} + + enum Status {NoDispute, WaitingSeller, WaitingBuyer, DisputeCreated, Resolved} struct Transaction { address seller; @@ -40,7 +50,7 @@ import "./Arbitrator.sol"; } /** @dev To be raised when a dispute is created. The main purpose of this event is to let the arbitrator know the meaning ruling IDs. - * @param _transactionId The index of the transaction in dispute + * @param _transactionId The index of the transaction in dispute. * @param _arbitrator The arbitrator of the contract. * @param _disputeID ID of the dispute in the Arbitrator contract. * @param _rulingOptions Map ruling IDs to short description of the ruling in a CSV format using ";" as a delimiter. Note that ruling IDs start a 1. For example "Send funds to buyer;Send funds to seller", means that ruling 1 will make the contract send funds to the buyer and 2 to the seller. @@ -48,15 +58,15 @@ import "./Arbitrator.sol"; event Dispute(uint indexed _transactionId, Arbitrator indexed _arbitrator, uint indexed _disputeID, string _rulingOptions); /** @dev To be raised when a ruling is given. - * @param _transactionId The index of the transaction in dispute + * @param _transactionId The index of the transaction in dispute. * @param _arbitrator The arbitrator giving the ruling. * @param _disputeID ID of the dispute in the Arbitrator contract. * @param _ruling The ruling which was given. */ event Ruling(uint indexed _transactionId, Arbitrator indexed _arbitrator, uint indexed _disputeID, uint _ruling); - /** @dev To be raised when evidence are submitted. Should point to the ressource (evidences are not to be stored on chain due tp gas considerations). - * @param _transactionId The index of the transaction + /** @dev To be raised when evidence are submitted. Should point to the ressource (evidences are not to be stored on chain due to gas considerations). + * @param _transactionId The index of the transaction. * @param _arbitrator The arbitrator of the contract. * @param _disputeID ID of the dispute in the Arbitrator contract. * @param _party The address of the party submiting the evidence. Note that 0 is kept for evidences not submitted by any party. @@ -66,12 +76,18 @@ import "./Arbitrator.sol"; /** @dev To be emmited at contract creation. Contains the hash of the plain text contract. This will allow any party to show what was the original contract. * This event is used as cheap way of storing it. - * @param _transactionId The index of the transaction + * @param _transactionId The index of the transaction. * @param _contractHash Keccak256 hash of the plain text contract. */ event ContractHash(uint indexed _transactionId, bytes32 _contractHash); - /** @dev Give a ruling for a dispute. Must be call by the arbitrator. + /** @dev Indicate that a party has to pay a fee or would otherwise be considered as loosing. + * @param _transactionId The index of the transaction. + * @param _party The party who has to pay. + */ + event HasToPayFee(uint indexed _transactionId, Party _party); + + /** @dev Give a ruling for a dispute. Must be called by the arbitrator. * The purpose of this function is to ensure that the address calling it has the right to rule on the contract. * @param _disputeID ID of the dispute in the Arbitrator contract. * @param _ruling Ruling given by the arbitrator. Note that 0 is reserved for "Not able/wanting to make a decision". @@ -79,36 +95,20 @@ import "./Arbitrator.sol"; function rule(uint _disputeID, uint _ruling) public { uint transactionId = disputeTxMap[keccak256(msg.sender,_disputeID)]; Transaction storage transaction = transactions[transactionId]; - require(msg.sender==address(transaction.arbitrator)); // Only arbitrator. + require(msg.sender==address(transaction.arbitrator)); Ruling(transactionId, Arbitrator(msg.sender),_disputeID,_ruling); executeRuling(_disputeID,_ruling); } - enum Status {NoDispute, WaitingSeller, WaitingBuyer, DisputeCreated, Resolved} - - uint8 constant AMOUNT_OF_CHOICES = 2; - uint8 constant BUYER_WINS = 1; - uint8 constant SELLER_WINS = 2; - - enum Party {Seller, Buyer} - - /** @dev Indicate that a party has to pay a fee or would otherwise be considered as loosing. - * @param _transactionId The index of the transaction - * @param _party The party who has to pay. - */ - event HasToPayFee(uint indexed _transactionId, Party _party); - - /** @dev Pay the arbitration fee to raise a dispute. To be called by the seller. UNTRUSTED. * Note that the arbitrator can have createDispute throw, which will make this function throw and therefore lead to a party being timed-out. * This is not a vulnerability as the arbitrator can rule in favor of one party anyway. - * @param _transactionId The index of the transaction + * @param _transactionId The index of the transaction. */ function payArbitrationFeeBySeller(uint _transactionId) payable { Transaction storage transaction = transactions[_transactionId]; - //onlySeller require(msg.sender == transaction.seller); @@ -126,14 +126,12 @@ import "./Arbitrator.sol"; } } - /** @dev Pay the arbitration fee to raise a dispute. To be called by the buyer. UNTRUSTED. * Note that this function mirror payArbitrationFeeBySeller. - * @param _transactionId The index of the transaction + * @param _transactionId The index of the transaction. */ function payArbitrationFeeByBuyer(uint _transactionId) payable { Transaction storage transaction = transactions[_transactionId]; - //onlyBuyer require(msg.sender == transaction.buyer); uint arbitrationCost = transaction.arbitrator.arbitrationCost(transaction.arbitratorExtraData); @@ -149,8 +147,9 @@ import "./Arbitrator.sol"; raiseDispute(_transactionId, arbitrationCost); } } + /** @dev Create a dispute. UNTRUSTED. - * @param _transactionId The index of the transaction + * @param _transactionId The index of the transaction. * @param _arbitrationCost Amount to pay the arbitrator. */ function raiseDispute(uint _transactionId, uint _arbitrationCost) internal { @@ -163,11 +162,10 @@ import "./Arbitrator.sol"; } /** @dev Reimburse partyA if partyB fails to pay the fee. - * @param _transactionId The index of the transaction + * @param _transactionId The index of the transaction. */ function timeOutBySeller(uint _transactionId) { Transaction storage transaction = transactions[_transactionId]; - //onlyPartyA require(msg.sender == transaction.seller); @@ -178,11 +176,10 @@ import "./Arbitrator.sol"; } /** @dev Pay partyB if partyA fails to pay the fee. - * @param _transactionId The index of the transaction + * @param _transactionId The index of the transaction. */ function timeOutByBuyer(uint _transactionId) { Transaction storage transaction = transactions[_transactionId]; - //onlyPartyB require(msg.sender == transaction.buyer); @@ -193,12 +190,11 @@ import "./Arbitrator.sol"; } /** @dev Submit a reference to evidence. EVENT. - * @param _transactionId The index of the transaction + * @param _transactionId The index of the transaction. * @param _evidence A link to an evidence using its URI. */ function submitEvidence(uint _transactionId, string _evidence) { Transaction storage transaction = transactions[_transactionId]; - //onlyParty require(msg.sender == transaction.buyer || msg.sender == transaction.seller); require(transaction.status>=Status.DisputeCreated); @@ -208,12 +204,11 @@ import "./Arbitrator.sol"; /** @dev Appeal an appealable ruling. * Transfer the funds to the arbitrator. * Note that no checks are required as the checks are done by the arbitrator. - * @param _transactionId The index of the transaction + * @param _transactionId The index of the transaction. * @param _extraData Extra data for the arbitrator appeal procedure. */ function appeal(uint _transactionId, bytes _extraData) payable public { Transaction storage transaction = transactions[_transactionId]; - //onlyParty require(msg.sender == transaction.buyer || msg.sender == transaction.seller); transaction.arbitrator.appeal.value(msg.value)(transaction.disputeId,_extraData); @@ -238,11 +233,8 @@ import "./Arbitrator.sol"; arbitrator: _arbitrator, arbitratorExtraData: _arbitratorExtraData, disputeId: 0, - // Total fees paid by the partyA. sellerFee: 0, - // Total fees paid by the partyB. buyerFee: 0, - // Last interaction for the dispute procedure. lastInteraction: now, status: Status.NoDispute })); @@ -256,7 +248,6 @@ import "./Arbitrator.sol"; function lastTransactionId () constant public returns (uint index) { uint len = transactions.length; for (uint i = len - 1; i>=0; i--) { - // do something if (transactions[i].buyer == msg.sender) { return i; } @@ -264,23 +255,21 @@ import "./Arbitrator.sol"; } /** @dev - * returns the transaction amount. Can be called by buyer, seller or arbitrator. - * @param _transactionId The index of the transaction + * @return amount The transaction amount. Can be called by buyer, seller or arbitrator. + * @param _transactionId The index of the transaction. */ function amount (uint _transactionId) constant public returns (uint amount) { Transaction storage transaction = transactions[_transactionId]; - //onlyParty or arbitrator require(msg.sender == transaction.buyer || msg.sender == transaction.seller || msg.sender == address(transaction.arbitrator)); + return transaction.amount; } - /** @dev - * transfer the transaction's amount to the seller if the timeout has passed - * @param _transactionId The index of the transaction + /** @dev Transfer the transaction's amount to the seller if the timeout has passed + * @param _transactionId The index of the transaction. */ function withdraw(uint _transactionId) public { Transaction storage transaction = transactions[_transactionId]; - //onlySeller ifPastTimeout require(msg.sender == transaction.seller); require(now>=transaction.lastInteraction+transaction.timeout); @@ -291,13 +280,14 @@ import "./Arbitrator.sol"; } /** @dev Reimburse party A. To be called if the good or service can't be fully provided. - * @param _transactionId The index of the transaction + * @param _transactionId The index of the transaction. * @param _amountReimbursed Amount to reimburse in wei. */ function reimburse(uint _transactionId, uint _amountReimbursed) public { Transaction storage transaction = transactions[_transactionId]; - require(transaction.seller == msg.sender); //onlySeller + require(transaction.seller == msg.sender); require(_amountReimbursed <= transaction.amount); + transaction.buyer.transfer(_amountReimbursed); transaction.amount -= _amountReimbursed; } From be54809fff6d21ff102b1c6584be13885d03be2c Mon Sep 17 00:00:00 2001 From: Eduardo Burgos Date: Wed, 27 Jun 2018 21:20:15 -0400 Subject: [PATCH 6/7] fix pull request issues by @clesaege --- .../MultipleArbitrableTransaction.sol | 53 +- test/multipleArbitrableTransaction.js | 1296 ++++++++++++----- 2 files changed, 981 insertions(+), 368 deletions(-) diff --git a/contracts/standard/arbitration/MultipleArbitrableTransaction.sol b/contracts/standard/arbitration/MultipleArbitrableTransaction.sol index d47d7eeb..7c531d7e 100644 --- a/contracts/standard/arbitration/MultipleArbitrableTransaction.sol +++ b/contracts/standard/arbitration/MultipleArbitrableTransaction.sol @@ -31,7 +31,6 @@ import "./Arbitrator.sol"; uint256 amount; uint256 timeout; // Time in seconds a party can take before being considered unresponding and lose the dispute. uint disputeId; - bool disputed; Arbitrator arbitrator; bytes arbitratorExtraData; uint sellerFee; // Total fees paid by the seller. @@ -40,9 +39,11 @@ import "./Arbitrator.sol"; Status status; } - Transaction[] transactions; + Transaction[] public transactions; + + mapping (bytes32 => uint) public disputeTxMap; + - mapping (bytes32 => uint) disputeTxMap; /** @dev Constructor. */ @@ -66,13 +67,12 @@ import "./Arbitrator.sol"; event Ruling(uint indexed _transactionId, Arbitrator indexed _arbitrator, uint indexed _disputeID, uint _ruling); /** @dev To be raised when evidence are submitted. Should point to the ressource (evidences are not to be stored on chain due to gas considerations). - * @param _transactionId The index of the transaction. * @param _arbitrator The arbitrator of the contract. * @param _disputeID ID of the dispute in the Arbitrator contract. * @param _party The address of the party submiting the evidence. Note that 0 is kept for evidences not submitted by any party. * @param _evidence A link to evidence or if it is short the evidence itself. Can be web link ("http://X"), IPFS ("ipfs:/X") or another storing service (using the URI, see https://en.wikipedia.org/wiki/Uniform_Resource_Identifier ). One usecase of short evidence is to include the hash of the plain English contract. */ - event Evidence(uint indexed _transactionId, Arbitrator indexed _arbitrator, uint indexed _disputeID, address _party, string _evidence); + event Evidence(Arbitrator indexed _arbitrator, uint indexed _disputeID, address _party, string _evidence); /** @dev To be emmited at contract creation. Contains the hash of the plain text contract. This will allow any party to show what was the original contract. * This event is used as cheap way of storing it. @@ -97,7 +97,7 @@ import "./Arbitrator.sol"; Transaction storage transaction = transactions[transactionId]; require(msg.sender==address(transaction.arbitrator)); - Ruling(transactionId, Arbitrator(msg.sender),_disputeID,_ruling); + emit Ruling(transactionId, Arbitrator(msg.sender),_disputeID,_ruling); executeRuling(_disputeID,_ruling); } @@ -114,13 +114,13 @@ import "./Arbitrator.sol"; uint arbitrationCost = transaction.arbitrator.arbitrationCost(transaction.arbitratorExtraData); transaction.sellerFee += msg.value; - require(transaction.sellerFee == arbitrationCost); // Require that the total pay at least the arbitration cost. + require(transaction.sellerFee >= arbitrationCost); // Require that the total pay at least the arbitration cost. require(transaction.status < Status.DisputeCreated); // Make sure a dispute has not been created yet. transaction.lastInteraction = now; if (transaction.buyerFee < arbitrationCost) { // The partyB still has to pay. This can also happens if he has paid, but arbitrationCost has increased. transaction.status = Status.WaitingBuyer; - HasToPayFee(_transactionId, Party.Buyer); + emit HasToPayFee(_transactionId, Party.Buyer); } else { // The partyB has also paid the fee. We create the dispute raiseDispute(_transactionId, arbitrationCost); } @@ -136,13 +136,13 @@ import "./Arbitrator.sol"; uint arbitrationCost = transaction.arbitrator.arbitrationCost(transaction.arbitratorExtraData); transaction.buyerFee += msg.value; - require(transaction.buyerFee == arbitrationCost); // Require that the total pay at least the arbitration cost. + require(transaction.buyerFee >= arbitrationCost); // Require that the total pay at least the arbitration cost. require(transaction.status < Status.DisputeCreated); // Make sure a dispute has not been created yet. transaction.lastInteraction = now; if (transaction.sellerFee < arbitrationCost) { // The partyA still has to pay. This can also happens if he has paid, but arbitrationCost has increased. transaction.status = Status.WaitingSeller; - HasToPayFee(_transactionId, Party.Seller); + emit HasToPayFee(_transactionId, Party.Seller); } else { // The partyA has also paid the fee. We create the dispute raiseDispute(_transactionId, arbitrationCost); } @@ -156,9 +156,8 @@ import "./Arbitrator.sol"; Transaction storage transaction = transactions[_transactionId]; transaction.status = Status.DisputeCreated; transaction.disputeId = transaction.arbitrator.createDispute.value(_arbitrationCost)(AMOUNT_OF_CHOICES,transaction.arbitratorExtraData); - transaction.disputed = true; disputeTxMap[keccak256(transaction.arbitrator, transaction.disputeId)] = _transactionId; - Dispute(_transactionId, transaction.arbitrator, transaction.disputeId,RULING_OPTIONS); + emit Dispute(_transactionId, transaction.arbitrator, transaction.disputeId,RULING_OPTIONS); } /** @dev Reimburse partyA if partyB fails to pay the fee. @@ -198,7 +197,7 @@ import "./Arbitrator.sol"; require(msg.sender == transaction.buyer || msg.sender == transaction.seller); require(transaction.status>=Status.DisputeCreated); - Evidence(_transactionId, transaction.arbitrator,transaction.disputeId,msg.sender,_evidence); + emit Evidence(transaction.arbitrator,transaction.disputeId,msg.sender,_evidence); } /** @dev Appeal an appealable ruling. @@ -229,7 +228,6 @@ import "./Arbitrator.sol"; buyer: msg.sender, amount: msg.value, timeout: _timeout, - disputed: false, arbitrator: _arbitrator, arbitratorExtraData: _arbitratorExtraData, disputeId: 0, @@ -238,32 +236,10 @@ import "./Arbitrator.sol"; lastInteraction: now, status: Status.NoDispute })); - ContractHash(transactions.length - 1, _hashContract); + emit ContractHash(transactions.length - 1, _hashContract); return transactions.length - 1; } - /** @dev - * returns the last transaction id whose buyer is the sender - */ - function lastTransactionId () constant public returns (uint index) { - uint len = transactions.length; - for (uint i = len - 1; i>=0; i--) { - if (transactions[i].buyer == msg.sender) { - return i; - } - } - } - - /** @dev - * @return amount The transaction amount. Can be called by buyer, seller or arbitrator. - * @param _transactionId The index of the transaction. - */ - function amount (uint _transactionId) constant public returns (uint amount) { - Transaction storage transaction = transactions[_transactionId]; - require(msg.sender == transaction.buyer || msg.sender == transaction.seller || msg.sender == address(transaction.arbitrator)); - - return transaction.amount; - } /** @dev Transfer the transaction's amount to the seller if the timeout has passed * @param _transactionId The index of the transaction. @@ -271,7 +247,8 @@ import "./Arbitrator.sol"; function withdraw(uint _transactionId) public { Transaction storage transaction = transactions[_transactionId]; require(msg.sender == transaction.seller); - require(now>=transaction.lastInteraction+transaction.timeout); + require(now >= transaction.lastInteraction+transaction.timeout); + require(transaction.status == Status.NoDispute); transaction.seller.send(transaction.amount); transaction.amount = 0; diff --git a/test/multipleArbitrableTransaction.js b/test/multipleArbitrableTransaction.js index c07edb1d..400c6095 100644 --- a/test/multipleArbitrableTransaction.js +++ b/test/multipleArbitrableTransaction.js @@ -1,351 +1,987 @@ /* eslint-disable no-undef */ // Avoid the linter considering truffle elements as undef. -const { expectThrow, increaseTime } = require('../helpers/utils') -const MultipleArbitrableTransaction = artifacts.require('./MultipleArbitrableTransaction.sol') -const CentralizedArbitrator = artifacts.require('./CentralizedArbitrator.sol') - -contract('MultipleArbitrableTransaction', function (accounts) { - let payer = accounts[0] - let payee = accounts[1] - let arbitrator = accounts[2] - let other = accounts[3] - let amount = 1000 - let timeout = 100 - let arbitrationFee = 20 - let gasPrice = 5000000000 - let contractHash = 0x6aa0bb2779ab006be0739900654a89f1f8a2d7373ed38490a7cbab9c9392e1ff - - it('Should handle 1 transaction', async () => { - let multipleContract = await MultipleArbitrableTransaction.new({ from: payer }) - await multipleContract.createTransaction(0x0, contractHash, timeout, payee, 0x0, {from: payer, value: amount}) - let arbitrableTransactionId = (await multipleContract.lastTransactionId.call(undefined, { from: payer })).toNumber() - let payerBalanceBeforeReimbursment = web3.eth.getBalance(payer) - await multipleContract.reimburse(arbitrableTransactionId, 1000, {from: payee}) - let newPayerBalance = web3.eth.getBalance(payer) - let newContractBalance = web3.eth.getBalance(multipleContract.address) - let newAmount = await multipleContract.amount.call(arbitrableTransactionId, { from: payer }) - - assert.equal(newPayerBalance.toString(), payerBalanceBeforeReimbursment.plus(1000).toString(), 'The payer has not been reimbursed correctly') - assert.equal(newContractBalance.toNumber(), 0, 'Bad amount in the contract') - assert.equal(newAmount.toNumber(), 0, 'Amount not updated correctly') - }) - - it('Should handle 3 transaction', async () => { - let multipleContract = await MultipleArbitrableTransaction.new({ from: payer }) +const { expectThrow, increaseTime } = require("../helpers/utils"); +const MultipleArbitrableTransaction = artifacts.require( + "./MultipleArbitrableTransaction.sol" +); +const CentralizedArbitrator = artifacts.require("./CentralizedArbitrator.sol"); + +contract("MultipleArbitrableTransaction", function(accounts) { + let payer = accounts[0]; + let payee = accounts[1]; + let arbitrator = accounts[2]; + let other = accounts[3]; + let amount = 1000; + let timeout = 100; + let arbitrationFee = 20; + let gasPrice = 5000000000; + let contractHash = 0x6aa0bb2779ab006be0739900654a89f1f8a2d7373ed38490a7cbab9c9392e1ff; + + async function getLastTransaction(multipleContract, callback) { + const contractHashEvent = multipleContract.ContractHash(); + const awaitable = new Promise((resolve, reject) => { + const handler = contractHashEvent.watch((error, result) => { + contractHashEvent.stopWatching(); + if (!error) { + resolve(result); + } else { + reject(error); + } + }); + }); + await callback(); + return await awaitable; + } + + it("Should handle 1 transaction", async () => { + let multipleContract = await MultipleArbitrableTransaction.new({ + from: payer + }); + const lastTransaction = await getLastTransaction( + multipleContract, + async () => { + await multipleContract.createTransaction( + 0x0, + contractHash, + timeout, + payee, + 0x0, + { from: payer, value: amount } + ); + } + ); + + let arbitrableTransactionId = lastTransaction.args._transactionId.toNumber(); + let payerBalanceBeforeReimbursment = web3.eth.getBalance(payer); + await multipleContract.reimburse(arbitrableTransactionId, 1000, { + from: payee + }); + let newPayerBalance = web3.eth.getBalance(payer); + let newContractBalance = web3.eth.getBalance(multipleContract.address); + let newAmount = (await multipleContract.transactions( + arbitrableTransactionId + ))[2]; + + assert.equal( + newPayerBalance.toString(), + payerBalanceBeforeReimbursment.plus(1000).toString(), + "The payer has not been reimbursed correctly" + ); + assert.equal( + newContractBalance.toNumber(), + 0, + "Bad amount in the contract" + ); + assert.equal(newAmount.toNumber(), 0, "Amount not updated correctly"); + }); + + it("Should handle 3 transaction", async () => { + let multipleContract = await MultipleArbitrableTransaction.new({ + from: payer + }); for (var cnt = 0; cnt < 3; cnt += 1) { - await multipleContract.createTransaction(0x0, contractHash, timeout, payee, 0x0, {from: payer, value: amount}) - let arbitrableTransactionId = (await multipleContract.lastTransactionId.call(undefined, { from: payer })).toNumber() - let payerBalanceBeforeReimbursment = web3.eth.getBalance(payer) - await multipleContract.reimburse(arbitrableTransactionId, 1000, {from: payee}) - let newPayerBalance = web3.eth.getBalance(payer) - let newContractBalance = web3.eth.getBalance(multipleContract.address) - let newAmount = await multipleContract.amount.call(arbitrableTransactionId, { from: payer }) - - assert.equal(newPayerBalance.toString(), payerBalanceBeforeReimbursment.plus(1000).toString(), 'The payer has not been reimbursed correctly') - assert.equal(newContractBalance.toNumber(), 0, 'Bad amount in the contract') - assert.equal(newAmount.toNumber(), 0, 'Amount not updated correctly') + const lastTransaction = await getLastTransaction( + multipleContract, + async () => { + await multipleContract.createTransaction( + 0x0, + contractHash, + timeout, + payee, + 0x0, + { from: payer, value: amount } + ); + } + ); + + let arbitrableTransactionId = lastTransaction.args._transactionId.toNumber(); + + let payerBalanceBeforeReimbursment = web3.eth.getBalance(payer); + await multipleContract.reimburse(arbitrableTransactionId, 1000, { + from: payee + }); + let newPayerBalance = web3.eth.getBalance(payer); + let newContractBalance = web3.eth.getBalance(multipleContract.address); + let newAmount = (await multipleContract.transactions( + arbitrableTransactionId + ))[2]; + + assert.equal( + newPayerBalance.toString(), + payerBalanceBeforeReimbursment.plus(1000).toString(), + "The payer has not been reimbursed correctly" + ); + assert.equal( + newContractBalance.toNumber(), + 0, + "Bad amount in the contract" + ); + assert.equal(newAmount.toNumber(), 0, "Amount not updated correctly"); } - }) - - it('Should put 1000 wei in the contract', async () => { - let multipleContract = await MultipleArbitrableTransaction.new({ from: payer }) - await multipleContract.createTransaction(0x0, contractHash, timeout, payee, 0x0, {from: payer, value: amount}) - let arbitrableTransactionId = (await multipleContract.lastTransactionId.call(undefined, { from: payer })).toNumber() - - assert.equal(web3.eth.getBalance(multipleContract.address), 1000, "The contract hasn't received the wei correctly.") - - let amountSending = await multipleContract.amount(arbitrableTransactionId) - assert.equal(amountSending.toNumber(), 1000, "The contract hasn't updated its amount correctly.") - }) - - // Pay - it('The payee should withdraw', async () => { - let initialPayeeBalance = web3.eth.getBalance(payee) - let multipleContract = await MultipleArbitrableTransaction.new({ from: payer }) - //The timeout is set to 0 to be able to withdraw right away in the test - await multipleContract.createTransaction(0x0, contractHash, timeout, payee, 0x0, {from: payer, value: amount}) - increaseTime(timeout + 1) - let arbitrableTransactionId = (await multipleContract.lastTransactionId.call(undefined, { from: payer })).toNumber() - let tx = await multipleContract.withdraw(arbitrableTransactionId, {from: payee}) - let consumed = tx.receipt.gasUsed * 100000000000 - let newPayeeBalance = web3.eth.getBalance(payee) - assert.equal(newPayeeBalance.toString(), initialPayeeBalance.plus(1000 - consumed).toString(), "The payee hasn't been paid properly") - }) - - it('The payer should not withdraw', async () => { - let multipleContract = await MultipleArbitrableTransaction.new({ from: payer }) - //The timeout is set to 0 to be able to withdraw right away in the test - await multipleContract.createTransaction(0x0, contractHash, timeout, payee, 0x0, {from: payer, value: amount}) - let arbitrableTransactionId = (await multipleContract.lastTransactionId.call(undefined, { from: payer })).toNumber() - await expectThrow(multipleContract.withdraw(arbitrableTransactionId, {from: payer})) - }) + }); + + it("Should put 1000 wei in the contract", async () => { + let multipleContract = await MultipleArbitrableTransaction.new({ + from: payer + }); + const lastTransaction = await getLastTransaction( + multipleContract, + async () => { + await multipleContract.createTransaction( + 0x0, + contractHash, + timeout, + payee, + 0x0, + { from: payer, value: amount } + ); + } + ); + + let arbitrableTransactionId = lastTransaction.args._transactionId.toNumber(); + + assert.equal( + web3.eth.getBalance(multipleContract.address), + 1000, + "The contract hasn't received the wei correctly." + ); + let amountSending = (await multipleContract.transactions( + arbitrableTransactionId + ))[2]; + + assert.equal( + amountSending.toNumber(), + 1000, + "The contract hasn't updated its amount correctly." + ); + }); + + // Pay + it("The payee should withdraw", async () => { + let initialPayeeBalance = web3.eth.getBalance(payee); + let multipleContract = await MultipleArbitrableTransaction.new({ + from: payer + }); + const lastTransaction = await getLastTransaction( + multipleContract, + async () => { + await multipleContract.createTransaction( + 0x0, + contractHash, + timeout, + payee, + 0x0, + { from: payer, value: amount } + ); + } + ); + + let arbitrableTransactionId = lastTransaction.args._transactionId.toNumber(); + + increaseTime(timeout + 1); + let tx = await multipleContract.withdraw(arbitrableTransactionId, { + from: payee + }); + let consumed = tx.receipt.gasUsed * 100000000000; + let newPayeeBalance = web3.eth.getBalance(payee); + assert.equal( + newPayeeBalance.toString(), + initialPayeeBalance.plus(1000 - consumed).toString(), + "The payee hasn't been paid properly" + ); + }); + + it("The payer should not withdraw", async () => { + let multipleContract = await MultipleArbitrableTransaction.new({ + from: payer + }); + const lastTransaction = await getLastTransaction( + multipleContract, + async () => { + await multipleContract.createTransaction( + 0x0, + contractHash, + timeout, + payee, + 0x0, + { from: payer, value: amount } + ); + } + ); + let arbitrableTransactionId = lastTransaction.args._transactionId.toNumber(); + await expectThrow( + multipleContract.withdraw(arbitrableTransactionId, { from: payer }) + ); + }); // Reimburse - it('Should reimburse 507 to the payer', async () => { - let multipleContract = await MultipleArbitrableTransaction.new({ from: payer }) - //The timeout is set to 0 to be able to withdraw right away in the test - await multipleContract.createTransaction(0x0, contractHash, 0 /* timeout */, payee, 0x0, {from: payer, value: amount}) - let arbitrableTransactionId = (await multipleContract.lastTransactionId.call(undefined, { from: payer })).toNumber() - - let payerBalanceBeforeReimbursment = web3.eth.getBalance(payer) - await multipleContract.reimburse(arbitrableTransactionId, 507, {from: payee}) - let newPayerBalance = web3.eth.getBalance(payer) - let newContractBalance = web3.eth.getBalance(multipleContract.address) - let newAmount = await multipleContract.amount(arbitrableTransactionId) - - assert.equal(newPayerBalance.toString(), payerBalanceBeforeReimbursment.plus(507).toString(), 'The payer has not been reimbursed correctly') - assert.equal(newContractBalance.toNumber(), 493, 'Bad amount in the contract') - assert.equal(newAmount.toNumber(), 493, 'Amount not updated correctly') - }) - - it('Should reimburse 1000 (all) to the payer', async () => { - let multipleContract = await MultipleArbitrableTransaction.new({ from: payer }) - - await multipleContract.createTransaction(0x0, contractHash, timeout, payee, 0x0, {from: payer, value: amount}) - let arbitrableTransactionId = (await multipleContract.lastTransactionId.call(undefined, { from: payer })).toNumber() - - let payerBalanceBeforeReimbursment = web3.eth.getBalance(payer) - await multipleContract.reimburse(arbitrableTransactionId, 1000, {from: payee}) - let newPayerBalance = web3.eth.getBalance(payer) - let newContractBalance = web3.eth.getBalance(multipleContract.address) - let newAmount = await multipleContract.amount(arbitrableTransactionId, { from: payer }) - - assert.equal(newPayerBalance.toString(), payerBalanceBeforeReimbursment.plus(1000).toString(), 'The payer has not been reimbursed correctly') - assert.equal(newContractBalance.toNumber(), 0, 'Bad amount in the contract') - assert.equal(newAmount.toNumber(), 0, 'Amount not updated correctly') - }) - - it('Should fail if we try to reimburse more', async () => { - let multipleContract = await MultipleArbitrableTransaction.new({ from: payer }) - - await multipleContract.createTransaction(0x0, contractHash, timeout, payee, 0x0, {from: payer, value: amount}) - let arbitrableTransactionId = (await multipleContract.lastTransactionId.call(undefined, { from: payer })).toNumber() - await expectThrow(multipleContract.reimburse(arbitrableTransactionId, 1003, {from: payee})) - }) - - it('Should fail if the payer to it', async () => { - let multipleContract = await MultipleArbitrableTransaction.new({ from: payer }) - - await multipleContract.createTransaction(0x0, contractHash, timeout, payee, 0x0, {from: payer, value: amount}) - let arbitrableTransactionId = (await multipleContract.lastTransactionId.call(undefined, { from: payer })).toNumber() - await expectThrow(multipleContract.reimburse(arbitrableTransactionId, 1000, {from: payer})) - }) + it("Should reimburse 507 to the payer", async () => { + let multipleContract = await MultipleArbitrableTransaction.new({ + from: payer + }); + const lastTransaction = await getLastTransaction( + multipleContract, + async () => { + await multipleContract.createTransaction( + 0x0, + contractHash, + 0 /* timeout */, + payee, + 0x0, + { from: payer, value: amount } + ); + } + ); + let arbitrableTransactionId = lastTransaction.args._transactionId.toNumber(); + + let payerBalanceBeforeReimbursment = web3.eth.getBalance(payer); + await multipleContract.reimburse(arbitrableTransactionId, 507, { + from: payee + }); + let newPayerBalance = web3.eth.getBalance(payer); + let newContractBalance = web3.eth.getBalance(multipleContract.address); + let newAmount = (await multipleContract.transactions( + arbitrableTransactionId + ))[2]; + + assert.equal( + newPayerBalance.toString(), + payerBalanceBeforeReimbursment.plus(507).toString(), + "The payer has not been reimbursed correctly" + ); + assert.equal( + newContractBalance.toNumber(), + 493, + "Bad amount in the contract" + ); + assert.equal(newAmount.toNumber(), 493, "Amount not updated correctly"); + }); + + it("Should reimburse 1000 (all) to the payer", async () => { + let multipleContract = await MultipleArbitrableTransaction.new({ + from: payer + }); + + const lastTransaction = await getLastTransaction( + multipleContract, + async () => { + await multipleContract.createTransaction( + 0x0, + contractHash, + timeout, + payee, + 0x0, + { from: payer, value: amount } + ); + } + ); + let arbitrableTransactionId = lastTransaction.args._transactionId.toNumber(); + + let payerBalanceBeforeReimbursment = web3.eth.getBalance(payer); + await multipleContract.reimburse(arbitrableTransactionId, 1000, { + from: payee + }); + let newPayerBalance = web3.eth.getBalance(payer); + let newContractBalance = web3.eth.getBalance(multipleContract.address); + let newAmount = (await multipleContract.transactions( + arbitrableTransactionId + ))[2]; + + assert.equal( + newPayerBalance.toString(), + payerBalanceBeforeReimbursment.plus(1000).toString(), + "The payer has not been reimbursed correctly" + ); + assert.equal( + newContractBalance.toNumber(), + 0, + "Bad amount in the contract" + ); + assert.equal(newAmount.toNumber(), 0, "Amount not updated correctly"); + }); + + it("Should fail if we try to reimburse more", async () => { + let multipleContract = await MultipleArbitrableTransaction.new({ + from: payer + }); + + const lastTransaction = await getLastTransaction( + multipleContract, + async () => { + await multipleContract.createTransaction( + 0x0, + contractHash, + timeout, + payee, + 0x0, + { from: payer, value: amount } + ); + } + ); + let arbitrableTransactionId = lastTransaction.args._transactionId.toNumber(); + + await expectThrow( + multipleContract.reimburse(arbitrableTransactionId, 1003, { from: payee }) + ); + }); + + it("Should fail if the payer to tries to reimburse it", async () => { + let multipleContract = await MultipleArbitrableTransaction.new({ + from: payer + }); + + const lastTransaction = await getLastTransaction( + multipleContract, + async () => { + await multipleContract.createTransaction( + 0x0, + contractHash, + timeout, + payee, + 0x0, + { from: payer, value: amount } + ); + } + ); + let arbitrableTransactionId = lastTransaction.args._transactionId.toNumber(); + + await expectThrow( + multipleContract.reimburse(arbitrableTransactionId, 1000, { from: payer }) + ); + }); // executeRuling - it('Should reimburse the payer (including arbitration fee) when the arbitrator decides so', async () => { - let centralizedArbitrator = await CentralizedArbitrator.new(arbitrationFee, {from: arbitrator}) - - let multipleContract = await MultipleArbitrableTransaction.new({ from: payer }) - - await multipleContract.createTransaction(centralizedArbitrator.address, contractHash, timeout, payee, 0x0, {from: payer, value: amount}) - let arbitrableTransactionId = (await multipleContract.lastTransactionId.call(undefined, { from: payer })).toNumber() - - await multipleContract.payArbitrationFeeBySeller(arbitrableTransactionId, {from: payee, value: arbitrationFee}) - await multipleContract.payArbitrationFeeByBuyer(arbitrableTransactionId, {from: payer, value: arbitrationFee}) - let payerBalanceBeforeReimbursment = web3.eth.getBalance(payer) - await centralizedArbitrator.giveRuling(0, 1, {from: arbitrator}) - let newPayerBalance = web3.eth.getBalance(payer) - assert.equal(newPayerBalance.toString(), payerBalanceBeforeReimbursment.plus(1020).toString(), 'The payer has not been reimbursed correctly') - }) - - it('Should pay the payee and reimburse him the arbitration fee when the arbitrator decides so', async () => { - let centralizedArbitrator = await CentralizedArbitrator.new(arbitrationFee, {from: arbitrator}) - let multipleContract = await MultipleArbitrableTransaction.new({ from: payer }) - - await multipleContract.createTransaction(centralizedArbitrator.address, contractHash, timeout, payee, 0x0, {from: payer, value: amount}) - let arbitrableTransactionId = (await multipleContract.lastTransactionId.call(undefined, { from: payer })).toNumber() - - await multipleContract.payArbitrationFeeByBuyer(arbitrableTransactionId, {from: payer, value: arbitrationFee}) - await multipleContract.payArbitrationFeeBySeller(arbitrableTransactionId, {from: payee, value: arbitrationFee}) - let payeeBalanceBeforePay = web3.eth.getBalance(payee) - await centralizedArbitrator.giveRuling(0, 2, {from: arbitrator}) - let newPayeeBalance = web3.eth.getBalance(payee) - assert.equal(newPayeeBalance.toString(), payeeBalanceBeforePay.plus(1020).toString(), 'The payee has not been paid properly') - }) - - it('It should do nothing if the arbitrator decides so', async () => { - let centralizedArbitrator = await CentralizedArbitrator.new(arbitrationFee, {from: arbitrator}) - let multipleContract = await MultipleArbitrableTransaction.new({ from: payer }) - - await multipleContract.createTransaction(centralizedArbitrator.address, contractHash, timeout, payee, 0x0, {from: payer, value: amount}) - let arbitrableTransactionId = (await multipleContract.lastTransactionId.call(undefined, { from: payer })).toNumber() - await multipleContract.payArbitrationFeeByBuyer(arbitrableTransactionId, {from: payer, value: arbitrationFee}) - await multipleContract.payArbitrationFeeBySeller(arbitrableTransactionId, {from: payee, value: arbitrationFee}) - let payeeBalanceBeforePay = web3.eth.getBalance(payee) - let payerBalanceBeforeReimbursment = web3.eth.getBalance(payer) - await centralizedArbitrator.giveRuling(0, 0, {from: arbitrator}) - let newPayeeBalance = web3.eth.getBalance(payee) - let newPayerBalance = web3.eth.getBalance(payer) - assert.equal(newPayeeBalance.toString(), payeeBalanceBeforePay.toString(), "The payee got wei while it shouldn't") - assert.equal(newPayerBalance.toString(), payerBalanceBeforeReimbursment.toString(), "The payer got wei while it shouldn't") - }) - - it('Should reimburse the payer in case of timeout of the payee', async () => { - let centralizedArbitrator = await CentralizedArbitrator.new(arbitrationFee, {from: arbitrator}) - - let multipleContract = await MultipleArbitrableTransaction.new({ from: payer }) - - await multipleContract.createTransaction(centralizedArbitrator.address, contractHash, timeout, payee, 0x0, {from: payer, value: amount}) - let arbitrableTransactionId = (await multipleContract.lastTransactionId.call(undefined, { from: payer })).toNumber() - await multipleContract.payArbitrationFeeByBuyer(arbitrableTransactionId, {from: payer, value: arbitrationFee}) - increaseTime(timeout + 1) - let payerBalanceBeforeReimbursment = web3.eth.getBalance(payer) - let tx = await multipleContract.timeOutByBuyer(arbitrableTransactionId, {from: payer, gasPrice: gasPrice}) - let txFee = tx.receipt.gasUsed * gasPrice - let newPayerBalance = web3.eth.getBalance(payer) - assert.equal(newPayerBalance.toString(), payerBalanceBeforeReimbursment.plus(1020).minus(txFee).toString(), 'The payer has not been reimbursed correctly') - }) + it("Should reimburse the payer (including arbitration fee) when the arbitrator decides so", async () => { + let centralizedArbitrator = await CentralizedArbitrator.new( + arbitrationFee, + { from: arbitrator } + ); + + let multipleContract = await MultipleArbitrableTransaction.new({ + from: payer + }); + + const lastTransaction = await getLastTransaction( + multipleContract, + async () => { + await multipleContract.createTransaction( + centralizedArbitrator.address, + contractHash, + timeout, + payee, + 0x0, + { from: payer, value: amount } + ); + } + ); + let arbitrableTransactionId = lastTransaction.args._transactionId.toNumber(); + + await multipleContract.payArbitrationFeeBySeller(arbitrableTransactionId, { + from: payee, + value: arbitrationFee + }); + await multipleContract.payArbitrationFeeByBuyer(arbitrableTransactionId, { + from: payer, + value: arbitrationFee + }); + let payerBalanceBeforeReimbursment = web3.eth.getBalance(payer); + await centralizedArbitrator.giveRuling(0, 1, { from: arbitrator }); + let newPayerBalance = web3.eth.getBalance(payer); + assert.equal( + newPayerBalance.toString(), + payerBalanceBeforeReimbursment.plus(1020).toString(), + "The payer has not been reimbursed correctly" + ); + }); + + it("Should pay the payee and reimburse him the arbitration fee when the arbitrator decides so", async () => { + let centralizedArbitrator = await CentralizedArbitrator.new( + arbitrationFee, + { from: arbitrator } + ); + let multipleContract = await MultipleArbitrableTransaction.new({ + from: payer + }); + + const lastTransaction = await getLastTransaction( + multipleContract, + async () => { + await multipleContract.createTransaction( + centralizedArbitrator.address, + contractHash, + timeout, + payee, + 0x0, + { from: payer, value: amount } + ); + } + ); + let arbitrableTransactionId = lastTransaction.args._transactionId.toNumber(); + + await multipleContract.payArbitrationFeeByBuyer(arbitrableTransactionId, { + from: payer, + value: arbitrationFee + }); + await multipleContract.payArbitrationFeeBySeller(arbitrableTransactionId, { + from: payee, + value: arbitrationFee + }); + let payeeBalanceBeforePay = web3.eth.getBalance(payee); + await centralizedArbitrator.giveRuling(0, 2, { from: arbitrator }); + let newPayeeBalance = web3.eth.getBalance(payee); + assert.equal( + newPayeeBalance.toString(), + payeeBalanceBeforePay.plus(1020).toString(), + "The payee has not been paid properly" + ); + }); + + it("It should do nothing if the arbitrator decides so", async () => { + let centralizedArbitrator = await CentralizedArbitrator.new( + arbitrationFee, + { from: arbitrator } + ); + let multipleContract = await MultipleArbitrableTransaction.new({ + from: payer + }); + + const lastTransaction = await getLastTransaction( + multipleContract, + async () => { + await multipleContract.createTransaction( + centralizedArbitrator.address, + contractHash, + timeout, + payee, + 0x0, + { from: payer, value: amount } + ); + } + ); + let arbitrableTransactionId = lastTransaction.args._transactionId.toNumber(); + + await multipleContract.payArbitrationFeeByBuyer(arbitrableTransactionId, { + from: payer, + value: arbitrationFee + }); + await multipleContract.payArbitrationFeeBySeller(arbitrableTransactionId, { + from: payee, + value: arbitrationFee + }); + let payeeBalanceBeforePay = web3.eth.getBalance(payee); + let payerBalanceBeforeReimbursment = web3.eth.getBalance(payer); + await centralizedArbitrator.giveRuling(0, 0, { from: arbitrator }); + let newPayeeBalance = web3.eth.getBalance(payee); + let newPayerBalance = web3.eth.getBalance(payer); + assert.equal( + newPayeeBalance.toString(), + payeeBalanceBeforePay.toString(), + "The payee got wei while it shouldn't" + ); + assert.equal( + newPayerBalance.toString(), + payerBalanceBeforeReimbursment.toString(), + "The payer got wei while it shouldn't" + ); + }); + + it("Should reimburse the payer in case of timeout of the payee", async () => { + let centralizedArbitrator = await CentralizedArbitrator.new( + arbitrationFee, + { from: arbitrator } + ); + + let multipleContract = await MultipleArbitrableTransaction.new({ + from: payer + }); + + const lastTransaction = await getLastTransaction( + multipleContract, + async () => { + await multipleContract.createTransaction( + centralizedArbitrator.address, + contractHash, + timeout, + payee, + 0x0, + { from: payer, value: amount } + ); + } + ); + let arbitrableTransactionId = lastTransaction.args._transactionId.toNumber(); + + await multipleContract.payArbitrationFeeByBuyer(arbitrableTransactionId, { + from: payer, + value: arbitrationFee + }); + increaseTime(timeout + 1); + let payerBalanceBeforeReimbursment = web3.eth.getBalance(payer); + let tx = await multipleContract.timeOutByBuyer(arbitrableTransactionId, { + from: payer, + gasPrice: gasPrice + }); + let txFee = tx.receipt.gasUsed * gasPrice; + let newPayerBalance = web3.eth.getBalance(payer); + assert.equal( + newPayerBalance.toString(), + payerBalanceBeforeReimbursment + .plus(1020) + .minus(txFee) + .toString(), + "The payer has not been reimbursed correctly" + ); + }); it("Shouldn't work before timeout for the payer", async () => { - let centralizedArbitrator = await CentralizedArbitrator.new(arbitrationFee, {from: arbitrator}) - - let multipleContract = await MultipleArbitrableTransaction.new({ from: payer }) - - await multipleContract.createTransaction(centralizedArbitrator.address, contractHash, timeout, payee, 0x0, {from: payer, value: amount}) - let arbitrableTransactionId = (await multipleContract.lastTransactionId.call(undefined, { from: payer })).toNumber() - await expectThrow(multipleContract.timeOutByBuyer(arbitrableTransactionId, {from: payer, gasPrice: gasPrice})) - await multipleContract.payArbitrationFeeByBuyer(arbitrableTransactionId, {from: payer, value: arbitrationFee}) - increaseTime(1) - await expectThrow(multipleContract.timeOutByBuyer(arbitrableTransactionId, {from: payer, gasPrice: gasPrice})) - }) - - it('Should pay and reimburse the payee in case of timeout of the payer', async () => { - let centralizedArbitrator = await CentralizedArbitrator.new(arbitrationFee, {from: arbitrator}) - - let multipleContract = await MultipleArbitrableTransaction.new({ from: payer }) - - await multipleContract.createTransaction(centralizedArbitrator.address, contractHash, timeout, payee, 0x0, {from: payer, value: amount}) - let arbitrableTransactionId = (await multipleContract.lastTransactionId.call(undefined, { from: payer })).toNumber() - - await multipleContract.payArbitrationFeeBySeller(arbitrableTransactionId, {from: payee, value: arbitrationFee}) - increaseTime(timeout + 1) - let payeeBalanceBeforeReimbursment = web3.eth.getBalance(payee) - let tx = await multipleContract.timeOutBySeller(arbitrableTransactionId, {from: payee, gasPrice: gasPrice}) - let txFee = tx.receipt.gasUsed * gasPrice - let newPayeeBalance = web3.eth.getBalance(payee) - assert.equal(newPayeeBalance.toString(), payeeBalanceBeforeReimbursment.plus(1020).minus(txFee).toString(), 'The payee has not been paid correctly') - }) + let centralizedArbitrator = await CentralizedArbitrator.new( + arbitrationFee, + { from: arbitrator } + ); + + let multipleContract = await MultipleArbitrableTransaction.new({ + from: payer + }); + + const lastTransaction = await getLastTransaction( + multipleContract, + async () => { + await multipleContract.createTransaction( + centralizedArbitrator.address, + contractHash, + timeout, + payee, + 0x0, + { from: payer, value: amount } + ); + } + ); + let arbitrableTransactionId = lastTransaction.args._transactionId.toNumber(); + + await expectThrow( + multipleContract.timeOutByBuyer(arbitrableTransactionId, { + from: payer, + gasPrice: gasPrice + }) + ); + await multipleContract.payArbitrationFeeByBuyer(arbitrableTransactionId, { + from: payer, + value: arbitrationFee + }); + increaseTime(1); + await expectThrow( + multipleContract.timeOutByBuyer(arbitrableTransactionId, { + from: payer, + gasPrice: gasPrice + }) + ); + }); + + it("Should pay and reimburse the payee in case of timeout of the payer", async () => { + let centralizedArbitrator = await CentralizedArbitrator.new( + arbitrationFee, + { from: arbitrator } + ); + + let multipleContract = await MultipleArbitrableTransaction.new({ + from: payer + }); + + const lastTransaction = await getLastTransaction( + multipleContract, + async () => { + await multipleContract.createTransaction( + centralizedArbitrator.address, + contractHash, + timeout, + payee, + 0x0, + { from: payer, value: amount } + ); + } + ); + let arbitrableTransactionId = lastTransaction.args._transactionId.toNumber(); + + await multipleContract.payArbitrationFeeBySeller(arbitrableTransactionId, { + from: payee, + value: arbitrationFee + }); + increaseTime(timeout + 1); + let payeeBalanceBeforeReimbursment = web3.eth.getBalance(payee); + let tx = await multipleContract.timeOutBySeller(arbitrableTransactionId, { + from: payee, + gasPrice: gasPrice + }); + let txFee = tx.receipt.gasUsed * gasPrice; + let newPayeeBalance = web3.eth.getBalance(payee); + assert.equal( + newPayeeBalance.toString(), + payeeBalanceBeforeReimbursment + .plus(1020) + .minus(txFee) + .toString(), + "The payee has not been paid correctly" + ); + }); it("Shouldn't work before timeout for the payee", async () => { - let centralizedArbitrator = await CentralizedArbitrator.new(arbitrationFee, {from: arbitrator}) - - let multipleContract = await MultipleArbitrableTransaction.new({ from: payer }) - - await multipleContract.createTransaction(centralizedArbitrator.address, contractHash, timeout, payee, 0x0, {from: payer, value: amount}) - let arbitrableTransactionId = (await multipleContract.lastTransactionId.call(undefined, { from: payer })).toNumber() - - await expectThrow(multipleContract.timeOutBySeller(arbitrableTransactionId, {from: payee, gasPrice: gasPrice})) - await multipleContract.payArbitrationFeeBySeller(arbitrableTransactionId, {from: payee, value: arbitrationFee}) - increaseTime(1) - await expectThrow(multipleContract.timeOutBySeller(arbitrableTransactionId, {from: payee, gasPrice: gasPrice})) - }) + let centralizedArbitrator = await CentralizedArbitrator.new( + arbitrationFee, + { from: arbitrator } + ); + + let multipleContract = await MultipleArbitrableTransaction.new({ + from: payer + }); + + const lastTransaction = await getLastTransaction( + multipleContract, + async () => { + await multipleContract.createTransaction( + centralizedArbitrator.address, + contractHash, + timeout, + payee, + 0x0, + { from: payer, value: amount } + ); + } + ); + let arbitrableTransactionId = lastTransaction.args._transactionId.toNumber(); + + await expectThrow( + multipleContract.timeOutBySeller(arbitrableTransactionId, { + from: payee, + gasPrice: gasPrice + }) + ); + await multipleContract.payArbitrationFeeBySeller(arbitrableTransactionId, { + from: payee, + value: arbitrationFee + }); + increaseTime(1); + await expectThrow( + multipleContract.timeOutBySeller(arbitrableTransactionId, { + from: payee, + gasPrice: gasPrice + }) + ); + }); // submitEvidence - it('Should create events when evidence is submitted by the payer', async () => { - let centralizedArbitrator = await CentralizedArbitrator.new(arbitrationFee, {from: arbitrator}) - - let multipleContract = await MultipleArbitrableTransaction.new({ from: payer }) - - await multipleContract.createTransaction(centralizedArbitrator.address, contractHash, timeout, payee, 0x0, {from: payer, value: amount}) - let arbitrableTransactionId = (await multipleContract.lastTransactionId.call(undefined, { from: payer })).toNumber() - await multipleContract.payArbitrationFeeByBuyer(arbitrableTransactionId, {from: payer, value: arbitrationFee}) - await multipleContract.payArbitrationFeeBySeller(arbitrableTransactionId, {from: payee, value: arbitrationFee}) - let tx = await multipleContract.submitEvidence(arbitrableTransactionId, 'ipfs:/X', {from: payer}) - assert.equal(tx.logs[0].event, 'Evidence') - assert.equal(tx.logs[0].args._arbitrator, centralizedArbitrator.address) - assert.equal(tx.logs[0].args._party, payer) - assert.equal(tx.logs[0].args._evidence, 'ipfs:/X') - }) - - it('Should create events when evidence is submitted by the payee', async () => { - let centralizedArbitrator = await CentralizedArbitrator.new(arbitrationFee, {from: arbitrator}) - - let multipleContract = await MultipleArbitrableTransaction.new({ from: payer }) - - await multipleContract.createTransaction(centralizedArbitrator.address, contractHash, timeout, payee, 0x0, {from: payer, value: amount}) - let arbitrableTransactionId = (await multipleContract.lastTransactionId.call(undefined, { from: payer })).toNumber() - await multipleContract.payArbitrationFeeByBuyer(arbitrableTransactionId, {from: payer, value: arbitrationFee}) - await multipleContract.payArbitrationFeeBySeller(arbitrableTransactionId, {from: payee, value: arbitrationFee}) - let tx = await multipleContract.submitEvidence(arbitrableTransactionId, 'ipfs:/X', {from: payee}) - assert.equal(tx.logs[0].event, 'Evidence') - assert.equal(tx.logs[0].args._arbitrator, centralizedArbitrator.address) - assert.equal(tx.logs[0].args._party, payee) - assert.equal(tx.logs[0].args._evidence, 'ipfs:/X') - }) - - it('Should fail if someone else try to submit', async () => { - let centralizedArbitrator = await CentralizedArbitrator.new(arbitrationFee, {from: arbitrator}) - - let multipleContract = await MultipleArbitrableTransaction.new({ from: payer }) - - await multipleContract.createTransaction(centralizedArbitrator.address, contractHash, timeout, payee, 0x0, {from: payer, value: amount}) - let arbitrableTransactionId = (await multipleContract.lastTransactionId.call(undefined, { from: payer })).toNumber() - - await multipleContract.payArbitrationFeeByBuyer(arbitrableTransactionId, {from: payer, value: arbitrationFee}) - await multipleContract.payArbitrationFeeBySeller(arbitrableTransactionId, {from: payee, value: arbitrationFee}) - await expectThrow(multipleContract.submitEvidence(arbitrableTransactionId, 'ipfs:/X', {from: other})) - }) - - - it('Should handle multiple transactions concurrently', async () => { - let centralizedArbitrator = await CentralizedArbitrator.new(arbitrationFee, {from: arbitrator}) - - let multipleContract = await MultipleArbitrableTransaction.new({ from: payer }) - - await multipleContract.createTransaction(centralizedArbitrator.address, contractHash, timeout, payee, 0x0, {from: payer, value: amount}) - let arbitrableTransactionId1 = (await multipleContract.lastTransactionId.call(undefined, { from: payer })).toNumber() - - await multipleContract.createTransaction(centralizedArbitrator.address, contractHash, timeout, payee, 0x0, {from: payer, value: amount}) - let arbitrableTransactionId2 = (await multipleContract.lastTransactionId.call(undefined, { from: payer })).toNumber() - - await multipleContract.payArbitrationFeeByBuyer(arbitrableTransactionId2, {from: payer, value: arbitrationFee}) - await multipleContract.payArbitrationFeeBySeller(arbitrableTransactionId1, {from: payee, value: arbitrationFee}) + it("Should create events when evidence is submitted by the payer", async () => { + let centralizedArbitrator = await CentralizedArbitrator.new( + arbitrationFee, + { from: arbitrator } + ); + + let multipleContract = await MultipleArbitrableTransaction.new({ + from: payer + }); + + const lastTransaction = await getLastTransaction( + multipleContract, + async () => { + await multipleContract.createTransaction( + centralizedArbitrator.address, + contractHash, + timeout, + payee, + 0x0, + { from: payer, value: amount } + ); + } + ); + let arbitrableTransactionId = lastTransaction.args._transactionId.toNumber(); + + await multipleContract.payArbitrationFeeByBuyer(arbitrableTransactionId, { + from: payer, + value: arbitrationFee + }); + await multipleContract.payArbitrationFeeBySeller(arbitrableTransactionId, { + from: payee, + value: arbitrationFee + }); + let tx = await multipleContract.submitEvidence( + arbitrableTransactionId, + "ipfs:/X", + { from: payer } + ); + assert.equal(tx.logs[0].event, "Evidence"); + assert.equal(tx.logs[0].args._arbitrator, centralizedArbitrator.address); + assert.equal(tx.logs[0].args._party, payer); + assert.equal(tx.logs[0].args._evidence, "ipfs:/X"); + }); + + it("Should create events when evidence is submitted by the payee", async () => { + let centralizedArbitrator = await CentralizedArbitrator.new( + arbitrationFee, + { from: arbitrator } + ); + + let multipleContract = await MultipleArbitrableTransaction.new({ + from: payer + }); + + const lastTransaction = await getLastTransaction( + multipleContract, + async () => { + await multipleContract.createTransaction( + centralizedArbitrator.address, + contractHash, + timeout, + payee, + 0x0, + { from: payer, value: amount } + ); + } + ); + let arbitrableTransactionId = lastTransaction.args._transactionId.toNumber(); + + await multipleContract.payArbitrationFeeByBuyer(arbitrableTransactionId, { + from: payer, + value: arbitrationFee + }); + await multipleContract.payArbitrationFeeBySeller(arbitrableTransactionId, { + from: payee, + value: arbitrationFee + }); + let tx = await multipleContract.submitEvidence( + arbitrableTransactionId, + "ipfs:/X", + { from: payee } + ); + assert.equal(tx.logs[0].event, "Evidence"); + assert.equal(tx.logs[0].args._arbitrator, centralizedArbitrator.address); + assert.equal(tx.logs[0].args._party, payee); + assert.equal(tx.logs[0].args._evidence, "ipfs:/X"); + }); + + it("Should fail if someone else try to submit", async () => { + let centralizedArbitrator = await CentralizedArbitrator.new( + arbitrationFee, + { from: arbitrator } + ); + + let multipleContract = await MultipleArbitrableTransaction.new({ + from: payer + }); + + const lastTransaction = await getLastTransaction( + multipleContract, + async () => { + await multipleContract.createTransaction( + centralizedArbitrator.address, + contractHash, + timeout, + payee, + 0x0, + { from: payer, value: amount } + ); + } + ); + let arbitrableTransactionId = lastTransaction.args._transactionId.toNumber(); + + await multipleContract.payArbitrationFeeByBuyer(arbitrableTransactionId, { + from: payer, + value: arbitrationFee + }); + await multipleContract.payArbitrationFeeBySeller(arbitrableTransactionId, { + from: payee, + value: arbitrationFee + }); + await expectThrow( + multipleContract.submitEvidence(arbitrableTransactionId, "ipfs:/X", { + from: other + }) + ); + }); + + it("Should handle multiple transactions concurrently", async () => { + let centralizedArbitrator = await CentralizedArbitrator.new( + arbitrationFee, + { from: arbitrator } + ); + + let multipleContract = await MultipleArbitrableTransaction.new({ + from: payer + }); + + const contractHashEvent = multipleContract.ContractHash(); + + let currentResolve; + const handler = contractHashEvent.watch((error, result) => { + currentResolve(result); + }); + + const transaction1Promise = new Promise(resolve => { + currentResolve = resolve; + + multipleContract.createTransaction( + centralizedArbitrator.address, + contractHash, + timeout, + payee, + 0x0, + { from: payer, value: amount } + ); + }); + + const lastTransaction = await transaction1Promise; + + let arbitrableTransactionId1 = lastTransaction.args._transactionId.toNumber(); + + const transaction2Promise = new Promise(resolve => { + currentResolve = resolve; + + multipleContract.createTransaction( + centralizedArbitrator.address, + contractHash, + timeout, + payee, + 0x0, + { from: payer, value: amount } + ); + }); + + const lastTransaction2 = await transaction2Promise; + + let arbitrableTransactionId2 = lastTransaction2.args._transactionId.toNumber(); + + contractHashEvent.stopWatching(); + + await multipleContract.payArbitrationFeeByBuyer(arbitrableTransactionId2, { + from: payer, + value: arbitrationFee + }); + await multipleContract.payArbitrationFeeBySeller(arbitrableTransactionId1, { + from: payee, + value: arbitrationFee + }); //This generates transaction 1 dispute 0 - await multipleContract.payArbitrationFeeByBuyer(arbitrableTransactionId1, {from: payer, value: arbitrationFee}) + await multipleContract.payArbitrationFeeByBuyer(arbitrableTransactionId1, { + from: payer, + value: arbitrationFee + }); //This generates transaction 2 dispute 1 - await multipleContract.payArbitrationFeeBySeller(arbitrableTransactionId2, {from: payee, value: arbitrationFee}) - - let payerBalanceBeforeReimbursment = web3.eth.getBalance(payer) - //Ruling for transaction 1 - await centralizedArbitrator.giveRuling(0, 1, {from: arbitrator}) - let newPayerBalance = web3.eth.getBalance(payer) - assert.equal(newPayerBalance.toString(), payerBalanceBeforeReimbursment.plus(1020).toString(), 'The payer has not been reimbursed correctly') + await multipleContract.payArbitrationFeeBySeller(arbitrableTransactionId2, { + from: payee, + value: arbitrationFee + }); - let payeeBalanceBeforePay = web3.eth.getBalance(payee) + let payerBalanceBeforeReimbursment = web3.eth.getBalance(payer); + //Ruling for transaction 1 + await centralizedArbitrator.giveRuling(0, 1, { from: arbitrator }); + let newPayerBalance = web3.eth.getBalance(payer); + assert.equal( + newPayerBalance.toString(), + payerBalanceBeforeReimbursment.plus(1020).toString(), + "The payer has not been reimbursed correctly" + ); + + let payeeBalanceBeforePay = web3.eth.getBalance(payee); //ruling for transaction 2 - await centralizedArbitrator.giveRuling(1, 2, {from: arbitrator}) - let newPayeeBalance = web3.eth.getBalance(payee) - assert.equal(newPayeeBalance.toString(), payeeBalanceBeforePay.plus(1020).toString(), 'The payee has not been paid properly') - }) - - it('Should handle multiple transactions and arbitrators concurrently', async () => { - let centralizedArbitrator1 = await CentralizedArbitrator.new(arbitrationFee, {from: arbitrator}) - let centralizedArbitrator2 = await CentralizedArbitrator.new(arbitrationFee, {from: other}) - - let multipleContract = await MultipleArbitrableTransaction.new({ from: payer }) - - await multipleContract.createTransaction(centralizedArbitrator1.address, contractHash, timeout, payee, 0x0, {from: payer, value: amount}) - let arbitrableTransactionId1 = (await multipleContract.lastTransactionId.call(undefined, { from: payer })).toNumber() - - await multipleContract.createTransaction(centralizedArbitrator2.address, contractHash, timeout, payee, 0x0, {from: payer, value: amount}) - let arbitrableTransactionId2 = (await multipleContract.lastTransactionId.call(undefined, { from: payer })).toNumber() - - await multipleContract.payArbitrationFeeByBuyer(arbitrableTransactionId2, {from: payer, value: arbitrationFee}) - await multipleContract.payArbitrationFeeBySeller(arbitrableTransactionId1, {from: payee, value: arbitrationFee}) + await centralizedArbitrator.giveRuling(1, 2, { from: arbitrator }); + let newPayeeBalance = web3.eth.getBalance(payee); + assert.equal( + newPayeeBalance.toString(), + payeeBalanceBeforePay.plus(1020).toString(), + "The payee has not been paid properly" + ); + }); + + it("Should handle multiple transactions and arbitrators concurrently", async () => { + let centralizedArbitrator1 = await CentralizedArbitrator.new( + arbitrationFee, + { from: arbitrator } + ); + let centralizedArbitrator2 = await CentralizedArbitrator.new( + arbitrationFee, + { from: other } + ); + + let multipleContract = await MultipleArbitrableTransaction.new({ + from: payer + }); + + const contractHashEvent = multipleContract.ContractHash(); + + let currentResolve; + const handler = contractHashEvent.watch((error, result) => { + currentResolve(result); + }); + + const transaction1Promise = new Promise(resolve => { + currentResolve = resolve; + + multipleContract.createTransaction( + centralizedArbitrator1.address, + contractHash, + timeout, + payee, + 0x0, + { from: payer, value: amount } + ); + }); + + const lastTransaction = await transaction1Promise; + + let arbitrableTransactionId1 = lastTransaction.args._transactionId.toNumber(); + + const transaction2Promise = new Promise(resolve => { + currentResolve = resolve; + + multipleContract.createTransaction( + centralizedArbitrator2.address, + contractHash, + timeout, + payee, + 0x0, + { from: payer, value: amount } + ); + }); + + const lastTransaction2 = await transaction2Promise; + + let arbitrableTransactionId2 = lastTransaction2.args._transactionId.toNumber(); + + contractHashEvent.stopWatching(); + + await multipleContract.payArbitrationFeeByBuyer(arbitrableTransactionId2, { + from: payer, + value: arbitrationFee + }); + await multipleContract.payArbitrationFeeBySeller(arbitrableTransactionId1, { + from: payee, + value: arbitrationFee + }); //This generates transaction 1 dispute 0 from arbitrator 1 - await multipleContract.payArbitrationFeeByBuyer(arbitrableTransactionId1, {from: payer, value: arbitrationFee}) + await multipleContract.payArbitrationFeeByBuyer(arbitrableTransactionId1, { + from: payer, + value: arbitrationFee + }); //This generates transaction 2 dispute 0 from arbitrator 2 - await multipleContract.payArbitrationFeeBySeller(arbitrableTransactionId2, {from: payee, value: arbitrationFee}) - - let payerBalanceBeforeReimbursment = web3.eth.getBalance(payer) - //Ruling for transaction 1 - await centralizedArbitrator1.giveRuling(0, 1, {from: arbitrator}) - let newPayerBalance = web3.eth.getBalance(payer) - assert.equal(newPayerBalance.toString(), payerBalanceBeforeReimbursment.plus(1020).toString(), 'The payer has not been reimbursed correctly') + await multipleContract.payArbitrationFeeBySeller(arbitrableTransactionId2, { + from: payee, + value: arbitrationFee + }); - let payeeBalanceBeforePay = web3.eth.getBalance(payee) + let payerBalanceBeforeReimbursment = web3.eth.getBalance(payer); + //Ruling for transaction 1 + await centralizedArbitrator1.giveRuling(0, 1, { from: arbitrator }); + let newPayerBalance = web3.eth.getBalance(payer); + assert.equal( + newPayerBalance.toString(), + payerBalanceBeforeReimbursment.plus(1020).toString(), + "The payer has not been reimbursed correctly" + ); + + let payeeBalanceBeforePay = web3.eth.getBalance(payee); //ruling for transaction 2 - await centralizedArbitrator2.giveRuling(0, 2, {from: other}) - let newPayeeBalance = web3.eth.getBalance(payee) - assert.equal(newPayeeBalance.toString(), payeeBalanceBeforePay.plus(1020).toString(), 'The payee has not been paid properly') - }) -}) + await centralizedArbitrator2.giveRuling(0, 2, { from: other }); + let newPayeeBalance = web3.eth.getBalance(payee); + assert.equal( + newPayeeBalance.toString(), + payeeBalanceBeforePay.plus(1020).toString(), + "The payee has not been paid properly" + ); + }); +}); From f88bc731efeb15677f42c4a46675436b4b53c818 Mon Sep 17 00:00:00 2001 From: Eduardo Burgos Date: Wed, 27 Jun 2018 21:46:29 -0400 Subject: [PATCH 7/7] fix some tests to ensure non-repeating events --- test/multipleArbitrableTransaction.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/test/multipleArbitrableTransaction.js b/test/multipleArbitrableTransaction.js index 400c6095..5e5eaa9c 100644 --- a/test/multipleArbitrableTransaction.js +++ b/test/multipleArbitrableTransaction.js @@ -808,8 +808,13 @@ contract("MultipleArbitrableTransaction", function(accounts) { const contractHashEvent = multipleContract.ContractHash(); let currentResolve; + let lastTransactionEvent = -1; const handler = contractHashEvent.watch((error, result) => { - currentResolve(result); + const eventTransaction = result.args._transactionId.toNumber(); + if (eventTransaction > lastTransactionEvent) { + lastTransactionEvent = eventTransaction; + currentResolve(result); + } }); const transaction1Promise = new Promise(resolve => { @@ -905,8 +910,13 @@ contract("MultipleArbitrableTransaction", function(accounts) { const contractHashEvent = multipleContract.ContractHash(); let currentResolve; + let lastTransactionEvent = -1; const handler = contractHashEvent.watch((error, result) => { - currentResolve(result); + const eventTransaction = result.args._transactionId.toNumber(); + if (eventTransaction > lastTransactionEvent) { + lastTransactionEvent = eventTransaction; + currentResolve(result); + } }); const transaction1Promise = new Promise(resolve => {