diff --git a/src/constants/error.js b/src/constants/error.js index 30730fd..edb2d98 100644 --- a/src/constants/error.js +++ b/src/constants/error.js @@ -33,6 +33,7 @@ export const UNABLE_TO_ACTIVATE_PNK = 'Unable to activate PNK, are you sure you have enough?' export const UNABLE_TO_FETCH_ARBITRATION_COST = 'Unable to fetch arbitration cost.' +export const UNABLE_TO_FETCH_APPEAL_COST = 'Unable to fetch appeal cost.' export const UNABLE_TO_FETCH_TIME_PER_PERIOD = 'Unable to fetch time per period' export const UNABLE_TO_PASS_PERIOD = 'Unable to pass period.' export const UNABLE_TO_FETCH_DISPUTE = 'Unable to fetch dispute.' @@ -52,6 +53,8 @@ export const DISPUTE_DOES_NOT_EXIST = disputeId => // ArbitrableTransaction export const UNABLE_TO_PAY_ARBITRATION_FEE = 'Unable to pay fee, are you sure you have enough PNK?' +export const UNABLE_TO_RAISE_AN_APPEAL = + 'Unable to raise appeal, are you sure you are in the appeal period?' export const UNABLE_TO_PAY_SELLER = 'Unable to pay the seller, are you sure you have enough ETH?' export const UNABLE_TO_CALL_TIMEOUT = 'Unable to call timeout.' diff --git a/src/contracts/implementations/arbitrable/ArbitrableTransaction.js b/src/contracts/implementations/arbitrable/ArbitrableTransaction.js index 6c3cbac..f7610e8 100644 --- a/src/contracts/implementations/arbitrable/ArbitrableTransaction.js +++ b/src/contracts/implementations/arbitrable/ArbitrableTransaction.js @@ -81,7 +81,7 @@ class ArbitrableTransaction extends ContractImplementation { /** * Pay the arbitration fee to raise a dispute. To be called by the party A. * @param {string} account - Ethereum account (default account[0]). - * @param {number} arbitrationCost - Amount to pay the arbitrator. (default 10000 wei). + * @param {number} arbitrationCost - Amount to pay the arbitrator. (default 0.15 ether). * @returns {object} - The result transaction object. */ payArbitrationFeeByPartyA = async ( @@ -215,6 +215,32 @@ class ArbitrableTransaction extends ContractImplementation { } } + /** + * Appeal an appealable ruling. + * @param {string} account Ethereum account (default account[1]). + * @param {bytes} extraData for the arbitrator appeal procedure. + * @param {number} appealCost Amount to pay the arbitrator. (default 0.35 ether). + * @returns {object} - The result transaction object. + */ + appeal = async ( + account = this._Web3Wrapper.getAccount(1), + extraData = 0x0, + appealCost = 0.35 + ) => { + await this.loadContract() + + try { + return this.contractInstance.appeal(extraData, { + from: account, + gas: ethConstants.TRANSACTION.GAS, + value: this._Web3Wrapper.toWei(appealCost, 'ether') + }) + } catch (err) { + console.error(err) + throw new Error(errorConstants.UNABLE_TO_RAISE_AN_APPEAL) + } + } + /** * Get ruling options from dispute via event * @param {string} arbitratorAddress address of arbitrator contract diff --git a/src/contracts/implementations/arbitrator/KlerosPOC.js b/src/contracts/implementations/arbitrator/KlerosPOC.js index 693b77b..f61b7aa 100644 --- a/src/contracts/implementations/arbitrator/KlerosPOC.js +++ b/src/contracts/implementations/arbitrator/KlerosPOC.js @@ -160,6 +160,28 @@ class KlerosPOC extends ContractImplementation { } } + /** + * Fetch the cost of appeal. + * @param {number} disputeId - index of the dispute. + * @param {bytes} contractExtraData - extra data from arbitrable contract. + * @returns {number} - The cost of appeal. + */ + getAppealCost = async (disputeId, contractExtraData) => { + await this.loadContract() + + try { + const appealCost = await this.contractInstance.appealCost( + disputeId, + contractExtraData + ) + + return this._Web3Wrapper.fromWei(appealCost, 'ether') + } catch (err) { + console.error(err) + throw new Error(errorConstants.UNABLE_TO_FETCH_APPEAL_COST) + } + } + /** * Call contract to move on to the next period. * @param {string} account - address of user. diff --git a/tests/integration/contracts.test.js b/tests/integration/contracts.test.js index a29b3cd..9d42f54 100644 --- a/tests/integration/contracts.test.js +++ b/tests/integration/contracts.test.js @@ -10,6 +10,8 @@ import delaySecond from '../helpers/delaySecond' describe('Contracts', () => { let partyA let partyB + let jurorContract1 + let jurorContract2 let other let web3 let klerosPOCData @@ -28,6 +30,10 @@ describe('Contracts', () => { partyB = web3.eth.accounts[1] other = web3.eth.accounts[2] + // these accounts are not used + jurorContract1 = web3.eth.accounts[11] + jurorContract2 = web3.eth.accounts[12] + klerosPOCData = { timesPerPeriod: [1, 1, 1, 1, 1], // activation, draw, vote, appeal, execution (seconds) account: other, @@ -245,5 +251,158 @@ describe('Contracts', () => { }, 50000 ) + it( + 'dispute with an appeal call by partyA', + async () => { + // deploy klerosPOC and arbitrableContract contracts + const [ + klerosPOCAddress, + arbitrableContractAddress + ] = await setUpContracts( + provider, + klerosPOCData, + arbitrableContractData + ) + expect(klerosPOCAddress).toBeDefined() + expect(arbitrableContractAddress).toBeDefined() + + const KlerosPOCInstance = new KlerosPOC(provider, klerosPOCAddress) + + // return a bigint + // FIXME use arbitrableTransaction + const ArbitrableTransactionInstance = new ArbitrableTransaction( + provider, + arbitrableContractAddress + ) + + // ****** Juror side (activate token) ****** // + + // jurors buy PNK + const buyPNKJurors = await Promise.all([ + KlerosPOCInstance.buyPNK(1, jurorContract1), + await KlerosPOCInstance.buyPNK(1, jurorContract2) + ]) + + const newBalance = await KlerosPOCInstance.getPNKBalance(jurorContract1) + + expect(newBalance.tokenBalance).toEqual(1) + + // activate PNK jurors + if (buyPNKJurors) { + await KlerosPOCInstance.activatePNK(1, jurorContract1) + await KlerosPOCInstance.activatePNK(1, jurorContract2) + } + + // load klerosPOC + const klerosPOCInstance = await KlerosPOCInstance.loadContract() + + // ****** Parties side (raise dispute) ****** // + + const arbitrableContractInstance = await ArbitrableTransactionInstance.loadContract() + + const partyAFeeContractInstance = await arbitrableContractInstance.partyAFee() + const partyBFeeContractInstance = await arbitrableContractInstance.partyBFee() + + // return bytes + // FIXME use arbitrableTransaction + let extraDataContractInstance = await arbitrableContractInstance.arbitratorExtraData() + + const KlerosInstance = new KlerosPOC(provider, klerosPOCAddress) + // return a bigint with the default value : 10000 wei fees in ether + const arbitrationCost = await KlerosInstance.getArbitrationCost( + extraDataContractInstance + ) + + // raise dispute party A + const raiseDisputeByPartyATxObj = await ArbitrableTransactionInstance.payArbitrationFeeByPartyA( + partyA, + arbitrationCost - + web3.fromWei(partyAFeeContractInstance, 'ether').toNumber() + ) + expect(raiseDisputeByPartyATxObj.tx).toEqual( + expect.stringMatching(/^0x[a-f0-9]{64}$/) + ) // tx hash + + // raise dispute party B + const raiseDisputeByPartyBTxObj = await ArbitrableTransactionInstance.payArbitrationFeeByPartyB( + partyB, + arbitrationCost - + web3.fromWei(partyBFeeContractInstance, 'ether').toNumber() + ) + expect(raiseDisputeByPartyBTxObj.tx).toEqual( + expect.stringMatching(/^0x[a-f0-9]{64}$/) + ) // tx hash + + // ****** Juror side (pass period) ****** // + + let newPeriod + // pass state so jurors are selected + for (let i = 1; i < 3; i++) { + // NOTE we need to make another block before we can generate the random number. Should not be an issue on main nets where avg block time < period length + if (i === 2) + web3.eth.sendTransaction({ + from: partyA, + to: partyB, + value: 10000, + data: '0x' + }) + await delaySecond() + await KlerosPOCInstance.passPeriod() + + newPeriod = await KlerosPOCInstance.getPeriod() + expect(newPeriod).toEqual(i) + } + + let drawA = [] + let drawB = [] + for (let i = 1; i <= 3; i++) { + if ( + await KlerosPOCInstance.isJurorDrawnForDispute(0, i, jurorContract1) + ) { + drawA.push(i) + } else { + drawB.push(i) + } + } + + const rulingJuror1 = 2 // vote for partyB + await KlerosPOCInstance.submitVotes( + 0, + rulingJuror1, + drawA, + jurorContract1 + ) + + const rulingJuror2 = 2 // vote for partyB + await KlerosPOCInstance.submitVotes( + 0, + rulingJuror2, + drawB, + jurorContract2 + ) + + await delaySecond() + await KlerosPOCInstance.passPeriod(other) + + const currentRuling = await klerosPOCInstance.currentRuling(0) + expect(`${currentRuling}`).toEqual('2') // partyB wins + + const appealCost = await KlerosPOCInstance.getAppealCost( + 0, + extraDataContractInstance + ) + + // raise appeal party A + const raiseAppealByPartyATxObj = await ArbitrableTransactionInstance.appeal( + partyA, + extraDataContractInstance, + appealCost + ) + expect(raiseAppealByPartyATxObj.tx).toEqual( + expect.stringMatching(/^0x[a-f0-9]{64}$/) + ) // tx hash + }, + 50000 + ) }) })