diff --git a/package.json b/package.json index 93b78d1..406ca22 100644 --- a/package.json +++ b/package.json @@ -82,9 +82,10 @@ "@kleros/archon": "^0.3.0", "@typeform/embed": "^0.5.12", "create-redux-form": "^0.1.2", + "eth-archon": "^0.2.0", "ethjs": "^0.3.3", "history": "^4.7.2", - "kleros-api": "^0.17.3", + "kleros-api-2": "^0.19.9", "lessdux": "^0.7.3", "normalize.css": "^7.0.0", "react": "^16.2.0", diff --git a/src/bootstrap/dapp-api.js b/src/bootstrap/dapp-api.js index b838063..a9c239b 100644 --- a/src/bootstrap/dapp-api.js +++ b/src/bootstrap/dapp-api.js @@ -1,5 +1,6 @@ import Eth from 'ethjs' -import { Kleros } from 'kleros-api' +import { Kleros } from 'kleros-api-2' // FIXME NPM hack +import Archon from '@kleros/archon' import * as ethConstants from '../constants/eth' @@ -31,6 +32,8 @@ const initializeKleros = async () => { kleros = new Kleros(eth.currentProvider, STORE_PROVIDER, ARBITRATOR_ADDRESS) } +const archon = new Archon(eth.currentProvider) + const ETHAddressRegExpCaptureGroup = '(0x[a-fA-F0-9]{40})' const ETHAddressRegExp = /0x[a-fA-F0-9]{40}/ const strictETHAddressRegExp = /^0x[a-fA-F0-9]{40}$/ @@ -44,7 +47,8 @@ export { ETHAddressRegExp, strictETHAddressRegExp, networkID, - env + env, + archon } setTimeout( diff --git a/src/components/iframes/doges-on-trial-evidence/index.js b/src/components/iframes/doges-on-trial-evidence/index.js index 8e18e14..b73f251 100644 --- a/src/components/iframes/doges-on-trial-evidence/index.js +++ b/src/components/iframes/doges-on-trial-evidence/index.js @@ -1,5 +1,5 @@ import React, { Component } from 'react' -import { ArbitrablePermissionList } from 'kleros-api/lib/contracts/implementations/arbitrable' +import { ArbitrablePermissionList } from 'kleros-api-2/lib/contracts/implementations/arbitrable' import { eth, env } from '../../../bootstrap/dapp-api' import LinkBox from '../../link-box' diff --git a/src/containers/dispute/components/details/index.js b/src/containers/dispute/components/details/index.js index 1776023..61d5463 100644 --- a/src/containers/dispute/components/details/index.js +++ b/src/containers/dispute/components/details/index.js @@ -1,13 +1,11 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' -import { ChainData } from '../../../../chainstrap' import { ARBITRATOR_ADDRESS } from '../../../../bootstrap/dapp-api' import { dateToString } from '../../../../utils/date' import { weiBNToDecimalString } from '../../../../utils/number' import LabelValueGroup from '../../../../components/label-value-group' import TruncatableTextBox from '../../../../components/truncatable-text-box' -import * as chainViewConstants from '../../../../constants/chain-view' import LinkBox from '../../../../components/link-box' import './details.css' @@ -24,12 +22,16 @@ class Details extends Component { message.data.target === 'evidence' && message.data.loaded ) { - const { metaEvidence, disputeID, arbitrableContractAddress } = this.props + const { + metaEvidenceJSON, + disputeID, + arbitrableContractAddress + } = this.props message.source.postMessage( { target: 'evidence', - metaEvidence, + metaEvidence: metaEvidenceJSON, evidence: null, arbitrableContractAddress, arbitratorAddress: ARBITRATOR_ADDRESS, @@ -42,28 +44,28 @@ class Details extends Component { render() { const { - date, + createdAt, arbitrationFee, - arbitrableContractAddress, - disputeID, appealNumber, - metaEvidence + metaEvidenceJSON } = this.props // Default display of primary document file. - let fileDisplay = ( + let fileDisplay = metaEvidenceJSON.fileURI ? (
{ - dispute.data.metaEvidence.rulingOptions + dispute.data.metaEvidenceJSON.rulingOptions .descriptions[0] }
@@ -211,7 +214,7 @@ class Dispute extends PureComponent { } > { - dispute.data.metaEvidence.rulingOptions + dispute.data.metaEvidenceJSON.rulingOptions .titles[0] } @@ -220,7 +223,7 @@ class Dispute extends PureComponent {{ - dispute.data.metaEvidence.rulingOptions + dispute.data.metaEvidenceJSON.rulingOptions .descriptions[1] }
@@ -242,7 +245,7 @@ class Dispute extends PureComponent { } > { - dispute.data.metaEvidence.rulingOptions + dispute.data.metaEvidenceJSON.rulingOptions .titles[1] } diff --git a/src/sagas/dispute.js b/src/sagas/dispute.js index e1e63d9..7cac9fb 100644 --- a/src/sagas/dispute.js +++ b/src/sagas/dispute.js @@ -2,26 +2,17 @@ import { makePopup } from '@typeform/embed' import { takeLatest, all, call, put, select } from 'redux-saga/effects' -import { addContract } from '../chainstrap' import * as disputeActions from '../actions/dispute' import * as arbitratorActions from '../actions/arbitrator' import * as walletSelectors from '../reducers/wallet' -import { kleros } from '../bootstrap/dapp-api' +import { kleros, archon, ARBITRATOR_ADDRESS } from '../bootstrap/dapp-api' import { lessduxSaga } from '../utils/saga' import { action } from '../utils/action' import * as disputeConstants from '../constants/dispute' -import * as chainViewConstants from '../constants/chain-view' import { fetchArbitratorData } from './arbitrator' const parseDispute = d => { - // Add arbitrable contract to ChainView - addContract({ - name: chainViewConstants.ARBITRABLE_CONTRACT_NAME, - address: d.arbitrableContractAddress, - color: chainViewConstants.ARBITRABLE_CONTRACT_COLOR - }) - // Find the latest appeal where the juror is drawn let latestAppealForJuror = null for (let i = d.appealJuror.length - 1; i >= 0; i--) @@ -30,6 +21,12 @@ const parseDispute = d => { break } + // Convert evidence timestamps to dates + d.evidence = d.evidence.map(e => { + e.submittedAt = new Date(e.submittedAt * 1000) + return e + }) + // Build array of appeals, evidence submissions, and rulings as events let events = [ ...d.appealJuror.slice(1).map((a, i) => ({ @@ -84,28 +81,41 @@ const parseDispute = d => { function* fetchDisputes() { const account = yield select(walletSelectors.getAccount) const [_disputes, arbitratorData] = yield all([ - call(kleros.arbitrator.getDisputesForUser, account), + call(kleros.arbitrator.getDisputesForUserFromStore, account), call(fetchArbitratorData) ]) + yield put( action(arbitratorActions.arbitratorData.RECEIVE, { arbitratorData }) ) const disputes = [] for (const d of _disputes) - if (d.arbitrableContractAddress && d.arbitrableContractAddress !== '0x') { - yield call( - kleros.arbitrable.setContractInstance, - d.arbitrableContractAddress - ) - - const metaEvidence = yield call(kleros.arbitrable.getMetaEvidence) + if (d.arbitrableContractAddress && d.arbitrableContractAddress !== '0x') + // legacy disputes, or disputes that do not follow the standard may not have MetaEvidence + try { + const disputeData = yield call( + archon.arbitrable.getDispute, + d.arbitrableContractAddress, + ARBITRATOR_ADDRESS, + d.disputeID + ) + const metaEvidence = yield call( + archon.arbitrable.getMetaEvidence, + d.arbitrableContractAddress, + disputeData.metaEvidenceID + ) + // TODO handle invalid hashes - disputes.push({ - ...d, - title: metaEvidence ? metaEvidence.title : null, - description: metaEvidence ? metaEvidence.description : null - }) - } else disputes.push(d) + disputes.push({ + ...d, + title: metaEvidence.metaEvidenceJSON.title || null, + description: metaEvidence.metaEvidenceJSON.description || null + }) + } catch (err) { + console.error(err) + disputes.push(d) + } + else disputes.push(d) return disputes } @@ -140,13 +150,125 @@ function* fetchDisputeDeadlines({ payload: { _disputes } }) { * @returns {object} - The dispute. */ function* fetchDispute({ payload: { disputeID } }) { - return parseDispute( - yield call( - kleros.disputes.getDataForDispute, - disputeID, - yield select(walletSelectors.getAccount) - ) + // Fetch extra data from contracts + const account = yield select(walletSelectors.getAccount) + + const [disputeData, session, period, jurorStoreData, netPNK] = yield all([ + call(kleros.arbitrator.getDispute, disputeID, true), + call(kleros.arbitrator.getSession), + call(kleros.arbitrator.getPeriod), + call(kleros.disputes.getDisputeFromStore, account, disputeID), + call(kleros.arbitrator.getNetTokensForDispute, disputeID, account) + ]) + + const disputeLogData = yield call( + archon.arbitrable.getDispute, + disputeData.arbitrableContractAddress, + ARBITRATOR_ADDRESS, + disputeID ) + + // Fetch Evidence and MetaEvidence + const [metaEvidence, evidence] = yield all([ + call( + archon.arbitrable.getMetaEvidence, + disputeData.arbitrableContractAddress, + disputeLogData.metaEvidenceID + ), + call( + archon.arbitrable.getEvidence, + disputeData.arbitrableContractAddress, + ARBITRATOR_ADDRESS, + disputeID + ) + ]) + + // fetch extra data needed for juror and ruling + const appealJuror = [] + const appealRulings = [] + for (let appeal = 0; appeal <= disputeData.numberOfAppeals; appeal++) { + // start fetching timestamps ASAP because they take the most time + const timestampPromises = [ + call(kleros.disputes.getAppealCreatedAt, disputeID, account, appeal), + call(kleros.disputes.getDisputeDeadline, disputeID, appeal), + call(kleros.disputes.getAppealRuledAt, disputeID, appeal) + ] + + const lastAppealSession = disputeData.firstSession + appeal + const isLastAppeal = lastAppealSession === session + // Get appeal data + const draws = jurorStoreData.appealDraws + ? jurorStoreData.appealDraws[appeal] || [] + : [] + let canRule = false + let canRepartition = false + let canExecute = false + let ruling = null + + const rulingPromises = [ + call(kleros.arbitrator.currentRulingForDispute, disputeID, appeal) + ] + + // Extra info for the last appeal + if (isLastAppeal) { + if (draws.length > 0) + rulingPromises.push( + call(kleros.arbitrator.canRuleDispute, disputeID, draws, account) + ) + + if (session && period) + canRepartition = + lastAppealSession <= session && // Not appealed to the next session + period === 4 && // Executable period + disputeData.state === 0 // Open dispute + canExecute = disputeData.state === 2 // Executable state + } + + // Wait for parallel requests to complete + ;[ruling, canRule] = yield all(rulingPromises) // es-lint-ignore prefer-const + + let jurorRuling = null // es-lint-ignore prefer-const + // if can't rule that means they already did or they missed it + if (draws.length > 0 && !canRule) + jurorRuling = yield call( + kleros.arbitrator.getVoteForJuror, + disputeID, + appeal, + account + ) + + // wait for all timestamps to be fetched + const [appealCreatedAt, appealDeadline, appealRuledAt] = yield all( + timestampPromises + ) + + appealJuror[appeal] = { + createdAt: appealCreatedAt, + fee: disputeData.arbitrationFeePerJuror.mul(draws.length), + draws, + jurorRuling, + canRule + } + appealRulings[appeal] = { + voteCounter: disputeData.voteCounters[appeal], + deadline: appealDeadline, + ruledAt: appealRuledAt, + ruling, + canRepartition, + canExecute + } + } + + const dispute = { + ...disputeData, + ...metaEvidence, + evidence, + appealJuror, + appealRulings, + netPNK + } + + return yield call(parseDispute, dispute) } /** diff --git a/yarn.lock b/yarn.lock index 434b381..fb94c87 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6650,6 +6650,21 @@ etag@~1.8.1: resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= +eth-archon@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/eth-archon/-/eth-archon-0.2.0.tgz#9e567d90ee0ba34d548dd3e0fa9586d848dcbf1f" + integrity sha512-AarVZlPUd47ckCvWaKjV03EfSzwDizFGGpL3mUqEAANZPIE18Sh2w0/hJ2T6Mw7EGL88GhAjzF4rythOw1qg4g== + dependencies: + axios "^0.18.0" + babel-runtime "^6.26.0" + js-sha3 "^0.8.0" + kleros-interaction "^0.0.24" + lodash "^4.17.4" + multihashes "^0.4.14" + nock "^10.0.0" + solc "^0.4.25" + web3 "^1.0.0-beta.36" + eth-contract-class@0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/eth-contract-class/-/eth-contract-class-0.0.6.tgz#398b8952149cc747cb959fa8b5d480288c7a8bce" @@ -7507,13 +7522,13 @@ findup-sync@0.4.2: resolve-dir "^0.1.0" flat-cache@^1.2.1: - version "1.3.2" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-1.3.2.tgz#7f852d70be573dac874a4c4129d340a34fba7e65" - integrity sha512-KByBY8c98sLUAGpnmjEdWTrtrLZRtZdwds+kAL/ciFXTCb7AZgqKsAnVnYFQj1hxepwO8JKN/8AsRWwLq+RK0A== + version "1.3.4" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-1.3.4.tgz#2c2ef77525cc2929007dfffa1dd314aa9c9dee6f" + integrity sha512-VwyB3Lkgacfik2vhqR4uv2rvebqmDvFu4jlN/C1RzWoJEo8I7z4Q404oiqYCkq41mni8EzQnm95emU9seckwtg== dependencies: circular-json "^0.3.1" - del "^3.0.0" graceful-fs "^4.1.2" + rimraf "~2.6.2" write "^0.2.1" flatmap-stream@^0.1.0: @@ -10363,17 +10378,17 @@ klaw@^1.0.0: optionalDependencies: graceful-fs "^4.1.9" -kleros-api@^0.17.3: - version "0.17.3" - resolved "https://registry.yarnpkg.com/kleros-api/-/kleros-api-0.17.3.tgz#3ba8770cdf1c702bb855a3bebd924d1ae63fbc23" - integrity sha512-90OGUc5s+PgNlSFc5YPk6njFi01TtMeJdZQfBS/m/6hy2oNnTnzRSggexmoUExxRCaKfIogtSREDpZzH6F5tnw== +kleros-api-2@^0.19.9: + version "0.19.9" + resolved "https://registry.yarnpkg.com/kleros-api-2/-/kleros-api-2-0.19.9.tgz#1aa7a320f2aec1734b41c343d4a66e7ab393d572" + integrity sha512-8RdhTACuFrX7fdocb3hpuU19WoB81ONHoj1NP8D/USmPdjW2XkwCUCW70hiklYN21BCSvsl4nlFe07bHiy+JFQ== dependencies: babel-runtime "^6.26.0" eth-sig-util "^1.4.2" ethereumjs-util "^5.2.0" ethjs "^0.4.0" kleros "^0.0.6" - kleros-interaction "^0.0.22" + kleros-interaction "^0.1.1" lodash "^4.17.4" minimetoken "^0.2.0" truffle-contract "^2.0.5" @@ -10388,14 +10403,6 @@ kleros-interaction@^0.0.15: minimetoken "^0.2.0" zeppelin-solidity "^1.7.0" -kleros-interaction@^0.0.22: - version "0.0.22" - resolved "https://registry.yarnpkg.com/kleros-interaction/-/kleros-interaction-0.0.22.tgz#abf006cc3c8d07e35246779e18ea3fabb47bf6a8" - integrity sha512-+zRd2A4dmpz/Z5H+fuoorYEItNwYTj3Ep8dr1weV//vxfajpLqCruiPV2iFXBr5yzGoFnrYW+HnKO5/Jo+M2/A== - dependencies: - minimetoken "^0.2.0" - zeppelin-solidity "^1.7.0" - kleros-interaction@^0.0.24: version "0.0.24" resolved "https://registry.yarnpkg.com/kleros-interaction/-/kleros-interaction-0.0.24.tgz#4bfd836b910ffee6634fffea0b6d85b44dbd3d92" @@ -10404,6 +10411,14 @@ kleros-interaction@^0.0.24: minimetoken "^0.2.0" zeppelin-solidity "^1.7.0" +kleros-interaction@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/kleros-interaction/-/kleros-interaction-0.1.1.tgz#a2c0a25a2f7c861c9b8bfabc792a19dd7353e8a2" + integrity sha512-i2mYXpa4BD8YgN4/F9PBvHa27P5WRKxWtILslDRIy77bZFYKRvieNhMZAlQepdnqOS4tPrrdNZv/yjCz1Ke5ag== + dependencies: + minimetoken "^0.2.0" + openzeppelin-solidity "^1.12.0" + kleros@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/kleros/-/kleros-0.0.6.tgz#d0397d9b1c796ed82a0455cb0fb73e7c25bd7284" @@ -12166,6 +12181,11 @@ onetime@^2.0.0: dependencies: mimic-fn "^1.0.0" +openzeppelin-solidity@^1.12.0: + version "1.12.0" + resolved "https://registry.yarnpkg.com/openzeppelin-solidity/-/openzeppelin-solidity-1.12.0.tgz#7b9c55975e73370d4541e3442b30cb3d91ac973a" + integrity sha512-WlorzMXIIurugiSdw121RVD5qA3EfSI7GybTn+/Du0mPNgairjt29NpVTAaH8eLjAeAwlw46y7uQKy0NYem/gA== + opn@5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/opn/-/opn-5.2.0.tgz#71fdf934d6827d676cecbea1531f95d354641225" @@ -15246,7 +15266,7 @@ right-pad@^1.0.1: resolved "https://registry.yarnpkg.com/right-pad/-/right-pad-1.0.1.tgz#8ca08c2cbb5b55e74dafa96bf7fd1a27d568c8d0" integrity sha1-jKCMLLtbVedNr6lr9/0aJ9VoyNA= -rimraf@2, rimraf@^2.2.8, rimraf@^2.5.2, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2: +rimraf@2, rimraf@^2.2.8, rimraf@^2.5.2, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@~2.6.2: version "2.6.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36" integrity sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==