diff --git a/CHANGELOG.md b/CHANGELOG.md
index 914bf2b..ea99c10 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,31 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
+
+## 0.0.62 (2018-02-28)
+
+
+### Bug Fixes
+
+* **getDeadlineForDispute:** fix wrong computation and return date object ([7ee05f5](https://github.com/kleros/kleros-api/commit/7ee05f5))
+* skip broken test assertions ([d210c53](https://github.com/kleros/kleros-api/commit/d210c53))
+* test suite ([98b777d](https://github.com/kleros/kleros-api/commit/98b777d))
+
+
+### Features
+
+* normalize token units ([d0d40f8](https://github.com/kleros/kleros-api/commit/d0d40f8))
+* **disputes:** build voteCounters and PNKRepartitions from getters ([ba48e67](https://github.com/kleros/kleros-api/commit/ba48e67))
+* **disputes:** change approach to getting netPNK and votesCounter ([366340f](https://github.com/kleros/kleros-api/commit/366340f))
+* **disputes:** createdAt and ruledAt timestamps added in events ([8e9cafa](https://github.com/kleros/kleros-api/commit/8e9cafa))
+* **disputes:** remove netPNK until events are fixed ([255bf03](https://github.com/kleros/kleros-api/commit/255bf03))
+* **disputes:** return appealsRepartitioned ([1246968](https://github.com/kleros/kleros-api/commit/1246968))
+* **disputes:** return deadline as epoch in ms instead of a date object ([90d4856](https://github.com/kleros/kleros-api/commit/90d4856))
+* **disputes:** return dispute status ([32db45c](https://github.com/kleros/kleros-api/commit/32db45c))
+* **disputes:** submittedAt timestamp for evidence ([3656d33](https://github.com/kleros/kleros-api/commit/3656d33))
+
+
+
## 0.0.61 (2018-02-27)
diff --git a/README.md b/README.md
index e0ab2cd..8ee99cc 100644
--- a/README.md
+++ b/README.md
@@ -3,8 +3,8 @@
+
-
diff --git a/package.json b/package.json
index 4a1bb38..3e17dd1 100644
--- a/package.json
+++ b/package.json
@@ -1,9 +1,12 @@
{
"name": "kleros-api",
- "version": "0.0.61",
- "description":
- "A Javascript library that makes it easy to build relayers and other DApps that use the Kleros protocol.",
- "keywords": ["Blockchain", "Ethereum", "Kleros"],
+ "version": "0.0.62",
+ "description": "A Javascript library that makes it easy to build relayers and other DApps that use the Kleros protocol.",
+ "keywords": [
+ "Blockchain",
+ "Ethereum",
+ "Kleros"
+ ],
"homepage": "https://kleros.io",
"repository": "github:kleros/kleros-api",
"bugs": "https://github.com/kleros/kleros-api/issues",
@@ -28,7 +31,9 @@
"build": "webpack --env.NODE_ENV=production -p"
},
"commitlint": {
- "extends": ["@commitlint/config-conventional"]
+ "extends": [
+ "@commitlint/config-conventional"
+ ]
},
"devDependencies": {
"babel-core": "^6.26.0",
diff --git a/src/abstractWrappers/Disputes.js b/src/abstractWrappers/Disputes.js
index d5df842..1dc421f 100644
--- a/src/abstractWrappers/Disputes.js
+++ b/src/abstractWrappers/Disputes.js
@@ -2,7 +2,7 @@ import _ from 'lodash'
import * as ethConstants from '../constants/eth'
import * as arbitratorConstants from '../constants/arbitrator'
-import * as contractConstants from '../constants/contract'
+import * as notificationConstants from '../constants/notification'
import AbstractWrapper from './AbstractWrapper'
@@ -36,7 +36,16 @@ class Disputes extends AbstractWrapper {
)
// if listener is a party in dispute add to store
if (disputeData.partyA === address || disputeData.partyB === address) {
- await this._updateStoreForDispute(contractAddress, disputeId, address)
+ const blockNumber = event.blockNumber
+ const block = this._Arbitrator._Web3Wrapper.getBlock(blockNumber)
+
+ // add new dispute with timestamp
+ await this._updateStoreForDispute(
+ contractAddress,
+ disputeId,
+ address,
+ block.timestamp * 1000
+ )
}
}
@@ -97,6 +106,127 @@ class Disputes extends AbstractWrapper {
)
}
+ /**
+ * Event listener that sends notification when a dispute has been ruled on.
+ * @param {string} arbitratorAddress - The arbitrator contract's address.
+ * @param {string} account - The users eth account.
+ * @param {function} callback - function to be called when event is triggered.
+ */
+ addDisputeRulingHandler = async (arbitratorAddress, account, callback) => {
+ if (!this._eventListener) return
+
+ const _disputeRulingHandler = async (
+ event,
+ contractAddress = arbitratorAddress,
+ address = account,
+ notificationCallback = callback
+ ) => {
+ const newPeriod = event.args._period.toNumber()
+ const txHash = event.transactionHash
+ // send appeal possible notifications
+ if (newPeriod === arbitratorConstants.PERIOD.APPEAL) {
+ this._checkArbitratorWrappersSet()
+ const userProfile = await this._StoreProvider.getUserProfile(address)
+ // contract data
+ const arbitratorData = await this._Arbitrator.getData(
+ contractAddress,
+ address
+ )
+ let disputeId = 0
+ const currentSession = arbitratorData.session
+
+ let dispute
+ while (1) {
+ // iterate over all disputes (FIXME inefficient)
+ try {
+ dispute = await this._Arbitrator.getDispute(
+ contractAddress,
+ disputeId
+ )
+ if (dispute.arbitratedContract === ethConstants.NULL_ADDRESS) break
+ // session + number of appeals
+ const disputeSession =
+ dispute.firstSession + dispute.numberOfAppeals
+ // if dispute not in current session skip
+ if (disputeSession !== currentSession) {
+ disputeId++
+ dispute = await this._Arbitrator.getDispute(
+ contractAddress,
+ disputeId
+ )
+ continue
+ }
+
+ const ruling = await this._Arbitrator.currentRulingForDispute(
+ contractAddress,
+ disputeId
+ )
+
+ if (
+ _.findIndex(
+ userProfile.disputes,
+ dispute =>
+ dispute.disputeId === disputeId &&
+ dispute.arbitratorAddress === contractAddress
+ ) >= 0
+ ) {
+ await this._StoreProvider.newNotification(
+ address,
+ txHash,
+ disputeId, // use disputeId instead of logIndex since it doens't have its own event
+ notificationConstants.TYPE.APPEAL_POSSIBLE,
+ 'A ruling has been made. Appeal is possible',
+ {
+ disputeId,
+ contractAddress,
+ ruling
+ }
+ )
+ // get ruledAt from block timestamp
+ const blockNumber = event.blockNumber
+ const block = this._Arbitrator._Web3Wrapper.getBlock(blockNumber)
+ // add ruledAt to store
+ await this._updateStoreForDispute(
+ contractAddress,
+ disputeId,
+ address,
+ null,
+ block.timestamp * 1000
+ )
+
+ if (notificationCallback) {
+ const userProfile = await this._StoreProvider.getUserProfile(
+ address
+ )
+ const notification = _.filter(
+ userProfile.notifications,
+ notification =>
+ notification.txHash === txHash &&
+ notification.logIndex === disputeId
+ )
+
+ if (notification) {
+ notificationCallback(notification[0])
+ }
+ }
+ }
+ // check next dispute
+ disputeId += 1
+ // eslint-disable-next-line no-unused-vars
+ } catch (err) {
+ // getDispute(n) throws an error if index out of range
+ break
+ }
+ }
+ }
+ }
+
+ await this._eventListener.registerArbitratorEvent(
+ 'NewPeriod',
+ _disputeRulingHandler
+ )
+ }
+
// **************************** //
// * Public * //
// **************************** //
@@ -123,7 +253,6 @@ class Disputes extends AbstractWrapper {
)
if (!txHash) throw new Error('unable to pay arbitration fee for party A')
- await this._storeNewDispute(arbitrableContractAddress, account)
return txHash
} catch (err) {
throw new Error(err)
@@ -151,39 +280,14 @@ class Disputes extends AbstractWrapper {
)
if (!txHash) throw new Error('unable to pay arbitration fee for party B')
- await this._storeNewDispute(arbitrableContractAddress, account)
return txHash
}
/**
- * If there is a dispute in contract update store.
- * @param {string} arbitrableContractAddress - The arbitrable contract's address.
- * @param {string} account - The account.
- */
- _storeNewDispute = async (arbitrableContractAddress, account) => {
- this._checkArbitratorWrappersSet()
- this._checkArbitrableWrappersSet()
-
- const arbitrableContractData = await this._ArbitrableContract.getData(
- arbitrableContractAddress
- )
-
- if (
- arbitrableContractData.status === contractConstants.STATUS.DISPUTE_CREATED
- ) {
- await this._updateStoreForDispute(
- arbitrableContractData.arbitrator,
- arbitrableContractData.disputeId,
- account
- )
- }
- }
-
- /**
- * Get disputes for user with extra data from arbitrated transaction and store.
- * @param {string} arbitratorAddress - Address of Kleros contract.
- * @param {string} account - Address of user.
- * @returns {object[]} - Dispute data objects for user.
+ * Get disputes for user with extra data from arbitrated transaction and store
+ * @param {string} arbitratorAddress address of Kleros contract
+ * @param {string} account address of user
+ * @returns {object[]} dispute data objects for user
*/
getDisputesForUser = async (arbitratorAddress, account) => {
// FIXME don't like having to call this every fnc
@@ -381,8 +485,8 @@ class Disputes extends AbstractWrapper {
/**
* Gets the deadline for an arbitrator's period, which is also the deadline for all its disputes.
* @param {string} arbitratorAddress - The address of the arbitrator contract.
- * @param {number} [period=arbitratorConstants.PERIOD.VOTE] - The period to get the deadline for.
- * @returns {Date} - A date object.
+ * @param {number} [period=PERIODS.VOTE] - The period to get the deadline for.
+ * @returns {number} - epoch timestamp
*/
getDeadlineForDispute = async (
arbitratorAddress,
@@ -404,8 +508,17 @@ class Disputes extends AbstractWrapper {
* @param {string} arbitratorAddress Address address of arbitrator contract
* @param {int} disputeId index of dispute
* @param {string} account address of party to update dispute or
+ * @param {number} createdAt epoch timestamp of when dispute was created
+ * @param {number} ruledAt epoch timestamp of when dispute was ruled on
+ * @returns {object} updated dispute object
*/
- _updateStoreForDispute = async (arbitratorAddress, disputeId, account) => {
+ _updateStoreForDispute = async (
+ arbitratorAddress,
+ disputeId,
+ account,
+ createdAt,
+ ruledAt
+ ) => {
const disputeData = await this.getDataForDispute(
arbitratorAddress,
disputeId,
@@ -413,10 +526,11 @@ class Disputes extends AbstractWrapper {
)
// update dispute
- await this._StoreProvider.updateDispute(
+ const dispute = await this._StoreProvider.updateDispute(
disputeData.disputeId,
disputeData.arbitratorAddress,
disputeData.hash,
+ disputeData.arbitrableContractAddress,
disputeData.partyA,
disputeData.partyB,
disputeData.title,
@@ -425,8 +539,10 @@ class Disputes extends AbstractWrapper {
disputeData.fee,
disputeData.information,
disputeData.justification,
- disputeData.resolutionOptions
- ).body
+ disputeData.resolutionOptions,
+ createdAt || disputeData.createdAt,
+ ruledAt || disputeData.ruledAt
+ )
// update profile for account
await this._StoreProvider.updateDisputeProfile(
@@ -435,9 +551,11 @@ class Disputes extends AbstractWrapper {
disputeData.arbitratorAddress,
disputeData.disputeId,
disputeData.votes.length > 0,
- false,
- 0
+ disputeData.hasRuled,
+ disputeData.netPNK ? disputeData.netPNK : 0
)
+
+ return dispute
}
/**
@@ -561,10 +679,12 @@ class Disputes extends AbstractWrapper {
let votes = []
let hasRuled = false
let netPNK = 0
+ let createdAt
+ let ruledAt
if (account) {
votes = await this.getVotesForJuror(arbitratorAddress, disputeId, account)
try {
- const userData = await this.getUserDisputeFromStore(
+ const userData = await this._StoreProvider.getDisputeData(
arbitratorAddress,
disputeId,
account
@@ -572,6 +692,8 @@ class Disputes extends AbstractWrapper {
isJuror = userData.isJuror
hasRuled = userData.hasRuled
netPNK = userData.netPNK
+ createdAt = userData.createdAt
+ ruledAt = userData.ruledAt
// eslint-disable-next-line no-unused-vars
} catch (err) {
// fetching dispute will fail if it hasn't been added to the store yet. this is ok we can just not return store data
@@ -622,7 +744,9 @@ class Disputes extends AbstractWrapper {
hasRuled,
ruling,
evidence,
- netPNK
+ netPNK,
+ ruledAt,
+ createdAt
}
}
}
diff --git a/src/abstractWrappers/Notifications.js b/src/abstractWrappers/Notifications.js
index e3b35c3..4e5b6c0 100644
--- a/src/abstractWrappers/Notifications.js
+++ b/src/abstractWrappers/Notifications.js
@@ -167,7 +167,6 @@ class Notifications extends AbstractWrapper {
dispute.arbitratorAddress,
dispute.disputeId
)
-
if (
disputeData.firstSession + disputeData.numberOfAppeals ===
currentSession
diff --git a/src/kleros.js b/src/kleros.js
index bcd9dba..4cbc6b5 100644
--- a/src/kleros.js
+++ b/src/kleros.js
@@ -85,7 +85,13 @@ class Kleros {
await this.disputes.addDisputeEventListener(arbitratorAddress, account)
await this.disputes.addTokenShiftToJurorProfileEventListener(
arbitratorAddress,
- account
+ account,
+ callback
+ )
+ await this.disputes.addDisputeRulingHandler(
+ arbitratorAddress,
+ account,
+ callback
)
await this.notifications.registerNotificationListeners(
arbitratorAddress,
diff --git a/src/utils/StoreProviderWrapper.js b/src/utils/StoreProviderWrapper.js
index 96f44db..706d15f 100644
--- a/src/utils/StoreProviderWrapper.js
+++ b/src/utils/StoreProviderWrapper.js
@@ -21,9 +21,8 @@ class StoreProviderWrapper {
let body = null
try {
body = JSON.parse(httpRequest.responseText)
- } catch (err) {
- console.log(err)
- }
+ // eslint-disable-next-line no-unused-vars
+ } catch (err) {}
resolve({
body: body,
status: httpRequest.status
@@ -80,7 +79,7 @@ class StoreProviderWrapper {
return httpResponse
}
- getDisputeData = async (userAddress, arbitratorAddress, disputeId) => {
+ getDisputeData = async (arbitratorAddress, disputeId, userAddress) => {
const userProfile = await this.getUserProfile(userAddress)
if (!userProfile)
throw new Error(`No profile found for address: ${userAddress}`)
@@ -91,7 +90,6 @@ class StoreProviderWrapper {
o.arbitratorAddress === arbitratorAddress && o.disputeId === disputeId
)
- if (_.isEmpty(disputeData)) return null
const httpResponse = await this._makeRequest(
'GET',
`${this._storeUri}/arbitrators/${arbitratorAddress}/disputes/${disputeId}`
@@ -154,13 +152,16 @@ class StoreProviderWrapper {
}
addEvidenceContract = async (address, account, name, description, url) => {
+ // get timestamp for submission
+ const submittedAt = new Date().getTime()
const httpResponse = await this._makeRequest(
'POST',
`${this._storeUri}/${account}/contracts/${address}/evidence`,
JSON.stringify({
name,
description,
- url
+ url,
+ submittedAt
})
)
@@ -218,7 +219,9 @@ class StoreProviderWrapper {
fee,
information,
justification,
- resolutionOptions
+ resolutionOptions,
+ createdAt,
+ ruledAt
) => {
const httpResponse = await this._makeRequest(
'POST',
@@ -238,10 +241,11 @@ class StoreProviderWrapper {
fee,
information,
justification,
- resolutionOptions
+ resolutionOptions,
+ createdAt,
+ ruledAt
})
)
-
return httpResponse
}
@@ -302,7 +306,6 @@ class StoreProviderWrapper {
data
})
)
-
return httpResponse
}
}
diff --git a/src/utils/Web3Wrapper.js b/src/utils/Web3Wrapper.js
index e42a585..a2025a1 100644
--- a/src/utils/Web3Wrapper.js
+++ b/src/utils/Web3Wrapper.js
@@ -32,6 +32,8 @@ class Web3Wrapper {
blockNumber = () => this._web3.eth.blockNumber
+ getBlock = blockNumber => this._web3.eth.getBlock(blockNumber)
+
doesContractExistAtAddressAsync = async address => {
const code = await this._web3.eth.getCode(address)
// Regex matches 0x0, 0x00, 0x in order to accommodate poorly implemented clients
diff --git a/tests/kleros.test.js b/tests/kleros.test.js
index 8c51f51..d788b5d 100644
--- a/tests/kleros.test.js
+++ b/tests/kleros.test.js
@@ -537,7 +537,6 @@ describe('Kleros', () => {
expect(resolutionOptions.length).toEqual(2)
// add an evidence for partyA
- // FIXME use arbitrableTransaction
const testName = 'test name'
const testDesc = 'test description'
const testURL = 'http://test.com'
@@ -563,6 +562,7 @@ describe('Kleros', () => {
)
expect(contractStoreData.evidences[0].url).toBe(testURL)
+ expect(contractStoreData.evidences[0].submittedAt).toBeTruthy()
// check initial state of contract
// FIXME var must be more explicit
@@ -580,7 +580,7 @@ describe('Kleros', () => {
})
let newState
- // pass state so juror1s are selected
+ // 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)
@@ -687,6 +687,7 @@ describe('Kleros', () => {
// delay 1 second
await delaySecond()
+
// move to execute period
await KlerosInstance.arbitrator.passPeriod(klerosCourt.address, other)
// stateful notifications
@@ -700,13 +701,13 @@ describe('Kleros', () => {
notificationConstants.TYPE.CAN_REPARTITION
)
- partyBStatefullNotifications = await KlerosInstance.notifications.getStatefulNotifications(
+ let partyAStatefullNotifications = await KlerosInstance.notifications.getStatefulNotifications(
klerosCourt.address,
- partyB,
+ partyA,
false
)
- expect(partyBStatefullNotifications.length).toEqual(1)
- expect(partyBStatefullNotifications[0].notificationType).toEqual(
+ expect(partyAStatefullNotifications.length).toEqual(1)
+ expect(partyAStatefullNotifications[0].notificationType).toEqual(
notificationConstants.TYPE.CAN_REPARTITION
)
@@ -731,13 +732,13 @@ describe('Kleros', () => {
notificationConstants.TYPE.CAN_EXECUTE
)
- partyBStatefullNotifications = await KlerosInstance.notifications.getStatefulNotifications(
+ partyAStatefullNotifications = await KlerosInstance.notifications.getStatefulNotifications(
klerosCourt.address,
- partyB,
+ partyA,
false
)
- expect(partyBStatefullNotifications.length).toEqual(1)
- expect(partyBStatefullNotifications[0].notificationType).toEqual(
+ expect(partyAStatefullNotifications.length).toEqual(1)
+ expect(partyAStatefullNotifications[0].notificationType).toEqual(
notificationConstants.TYPE.CAN_EXECUTE
)
@@ -779,12 +780,12 @@ describe('Kleros', () => {
)
expect(juror1StatefullNotifications.length).toEqual(0)
- partyBStatefullNotifications = await KlerosInstance.notifications.getStatefulNotifications(
+ partyAStatefullNotifications = await KlerosInstance.notifications.getStatefulNotifications(
klerosCourt.address,
- partyB,
+ partyA,
false
)
- expect(partyBStatefullNotifications.length).toEqual(0)
+ expect(partyAStatefullNotifications.length).toEqual(0)
expect(notifications.length).toBeTruthy()
// partyA got notifications
@@ -854,6 +855,16 @@ describe('Kleros', () => {
KlerosInstance.eventListener.stopWatchingArbitratorEvents(
klerosCourt.address
)
+
+ // make sure createdAt set
+ const disputeData = await KlerosInstance.disputes.getDataForDispute(
+ klerosCourt.address,
+ 0,
+ partyA
+ )
+
+ expect(disputeData.createdAt).toBeTruthy()
+ expect(disputeData.ruledAt).toBeTruthy()
},
80000
)