Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added MultipleArbitrableTransaction.sol and fixed issue #27 #32

Merged
merged 8 commits into from
Jun 29, 2018
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
297 changes: 297 additions & 0 deletions contracts/standard/arbitration/MultipleArbitrableTransaction.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,297 @@
/**
* @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";



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;
address buyer;
uint256 amount;
uint256 timeout; // Time in seconds a party can take before being considered unresponding and lose the dispute.
uint disputeId;
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[] public transactions;

mapping (bytes32 => uint) public 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 to gas considerations).
* @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(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 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".
*/
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));

emit Ruling(transactionId, Arbitrator(msg.sender),_disputeID,_ruling);

executeRuling(_disputeID,_ruling);
}

/** @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];
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;
emit 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];
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;
emit 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);
disputeTxMap[keccak256(transaction.arbitrator, transaction.disputeId)] = _transactionId;
emit 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];
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];
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];
require(msg.sender == transaction.buyer || msg.sender == transaction.seller);

require(transaction.status>=Status.DisputeCreated);
emit Evidence(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];
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,
arbitrator: _arbitrator,
arbitratorExtraData: _arbitratorExtraData,
disputeId: 0,
sellerFee: 0,
buyerFee: 0,
lastInteraction: now,
status: Status.NoDispute
}));
emit ContractHash(transactions.length - 1, _hashContract);
return transactions.length - 1;
}


/** @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];
require(msg.sender == transaction.seller);
require(now >= transaction.lastInteraction+transaction.timeout);
require(transaction.status == Status.NoDispute);

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);
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[keccak256(msg.sender,_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;
}
}
4 changes: 2 additions & 2 deletions contracts/standard/arbitration/TwoPartyArbitrable.sol
Original file line number Diff line number Diff line change
@@ -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<Status.DisputeCreated); // Make sure a dispute has not been created yet.

lastInteraction=now;
@@ -82,7 +82,7 @@ contract TwoPartyArbitrable is Arbitrable {
function payArbitrationFeeByPartyB() payable onlyPartyB {
uint arbitrationCost=arbitrator.arbitrationCost(arbitratorExtraData);
partyBFee+=msg.value;
require(partyBFee == arbitrationCost); // Require that the total pay at least the arbitration cost.
require(partyBFee >= arbitrationCost); // Require that the total pay at least the arbitration cost.
require(status<Status.DisputeCreated); // Make sure a dispute has not been created yet.

lastInteraction=now;
997 changes: 997 additions & 0 deletions test/multipleArbitrableTransaction.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,997 @@
/* 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;

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) {
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
});
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
});
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
});

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
});

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
});

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
});

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;
let lastTransactionEvent = -1;
const handler = contractHashEvent.watch((error, result) => {
const eventTransaction = result.args._transactionId.toNumber();
if (eventTransaction > lastTransactionEvent) {
lastTransactionEvent = eventTransaction;
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
});
//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"
);
});

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;
let lastTransactionEvent = -1;
const handler = contractHashEvent.watch((error, result) => {
const eventTransaction = result.args._transactionId.toNumber();
if (eventTransaction > lastTransactionEvent) {
lastTransactionEvent = eventTransaction;
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
});
//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"
);
});
});