diff --git a/package.json b/package.json index 8f69a69..38701a1 100644 --- a/package.json +++ b/package.json @@ -36,8 +36,8 @@ "@assemblyscript/loader": "^0.14.11", "@assemblyscript/node": "github:AssemblyScript/node", "@gnosis.pm/truffle-nice-tools": "^1.3.1", - "@graphprotocol/graph-cli": "^0.54.0", - "@graphprotocol/graph-ts": "^0.31.0", + "@graphprotocol/graph-cli": "^0.71.0", + "@graphprotocol/graph-ts": "^0.35.1", "@kleros/erc-792": "3.0.0", "@kleros/gtcr-encoder": "^1.1.3", "@kleros/tcr": "^2.0.0", @@ -62,7 +62,7 @@ "wait-on": "^5.2.0" }, "volta": { - "node": "16.20.0", + "node": "20.11.0", "yarn": "1.22.19" } } diff --git a/schema.graphql b/schema.graphql index 99710e4..fcc170f 100644 --- a/schema.graphql +++ b/schema.graphql @@ -54,6 +54,11 @@ type Evidence @entity { timestamp: BigInt! "Tx hash of the evidence submission" txHash: Bytes! + metadata: EvidenceMetadata +} + +type EvidenceMetadata @entity { + id: ID! "Name of the evidence" name: String "Title of the evidence" @@ -127,20 +132,6 @@ type LItem @entity { itemID: Bytes! "The data describing the item." data: String! - "The parsed data describing the item." - props: [ItemProp!]! @derivedFrom(field: "item") - "First indexable value of the json file." - key0: String - "Second indexable value of the json file." - key1: String - "Third indexable value of the json file." - key2: String - "Fourth indexable value of the json file." - key3: String - "Fifth indexable value of the json file." - key4: String - "The item identifiers combined as a single string." - keywords: String "The current status of the item." status: Status! "List of status change requests made for the item in the form requests[requestID]." @@ -161,14 +152,36 @@ type LItem @entity { latestRequester: Bytes! "The account that challenged the latest request, if any." latestChallenger: Bytes! + metadata: LItemMetadata } +type LItemMetadata @entity { + "ipfs cid" + id: ID! + "The parsed data describing the item." + props: [ItemProp!]! @derivedFrom(field: "item") + "First indexable value of the json file." + key0: String + "Second indexable value of the json file." + key1: String + "Third indexable value of the json file." + key2: String + "Fourth indexable value of the json file." + key3: String + "Fifth indexable value of the json file." + key4: String + "The item identifiers combined as a single string." + keywords: String + "The item this metadata belongs to" + item: LItem! @derivedFrom(field: "metadata") + +} type _Schema_ @fulltext( name: "itemSearch" language: en algorithm: rank - include: [{ entity: "LItem", fields: [{ name: "keywords" }] }] + include: [{ entity: "LItemMetadata", fields: [{ name: "keywords" }] }] ) type ItemProp @entity { @@ -178,7 +191,7 @@ type ItemProp @entity { description: String! isIdentifier: Boolean! value: String - item: LItem! + item: LItemMetadata! } type LRequest @entity { diff --git a/src/GeneralizedTCRMapping.ts b/src/GeneralizedTCRMapping.ts index 00da229..637f66c 100644 --- a/src/GeneralizedTCRMapping.ts +++ b/src/GeneralizedTCRMapping.ts @@ -1,12 +1,5 @@ /* eslint-disable prefer-const */ -import { - Bytes, - BigInt, - Address, - ipfs, - json, - log, -} from '@graphprotocol/graph-ts'; +import { Bytes, BigInt, Address, log } from '@graphprotocol/graph-ts'; import { Item, Request, @@ -36,6 +29,8 @@ import { Ruling, ConnectedTCRSet as ConnectedTCRSetEvent, } from '../generated/templates/GeneralizedTCR/GeneralizedTCR'; +import { GTCREvidenceMetadata as EvidenceMetadataTemplate } from '../generated/templates'; +import { extractPath } from './utils'; // Items on a TCR can be in 1 of 4 states: // - (0) Absent: The item is not registered on the TCR and there are no pending requests. @@ -616,80 +611,9 @@ export function handleEvidence(event: EvidenceEvent): void { BigInt.fromI32(1), ); - // Try to parse and store evidence fields. - let jsonStr = ipfs.cat(event.params._evidence); - if (!jsonStr) { - log.warning('Failed to fetch evidence {}', [event.params._evidence]); - evidenceGroup.save(); - evidence.save(); - return; - } - - let jsonObjValueAndSuccess = json.try_fromBytes(jsonStr as Bytes); - if (!jsonObjValueAndSuccess.isOk) { - log.warning(`Error getting json object value for evidence {}`, [ - event.params._evidence, - ]); - evidenceGroup.save(); - evidence.save(); - return; - } - - let jsonObj = jsonObjValueAndSuccess.value.toObject(); - if (!jsonObj) { - log.warning(`Error converting object for evidence {}`, [ - event.params._evidence, - ]); - evidenceGroup.save(); - evidence.save(); - return; - } - - let nameValue = jsonObj.get('name'); - if (!nameValue) { - log.warning(`Error getting name value for evidence {}`, [ - event.params._evidence, - ]); - } else { - evidence.name = nameValue.toString(); - } - - // Somehow Curate uses "title"?? so fetch in case - let titleValue = jsonObj.get('title'); - if (!titleValue) { - log.warning(`Error getting title value for evidence {}`, [ - event.params._evidence, - ]); - } else { - evidence.title = titleValue.toString(); - } - - let descriptionValue = jsonObj.get('description'); - if (!descriptionValue) { - log.warning(`Error getting description value for evidence {}`, [ - event.params._evidence, - ]); - } else { - evidence.description = descriptionValue.toString(); - } - - let fileURIValue = jsonObj.get('fileURI'); - if (!fileURIValue) { - log.warning(`Error getting fileURI value for evidence {}`, [ - event.params._evidence, - ]); - } else { - evidence.fileURI = fileURIValue.toString(); - } - - let fileTypeExtensionValue = jsonObj.get('fileTypeExtension'); - if (!fileTypeExtensionValue) { - log.warning(`Error getting fileTypeExtension value for evidence {}`, [ - event.params._evidence, - ]); - } else { - evidence.fileTypeExtension = fileTypeExtensionValue.toString(); - } + const ipfsHash = extractPath(event.params._evidence); + evidence.metadata = ipfsHash; + EvidenceMetadataTemplate.create(ipfsHash); evidenceGroup.save(); evidence.save(); diff --git a/src/LightGeneralizedTCRMapping.ts b/src/LightGeneralizedTCRMapping.ts index c033579..10cb155 100644 --- a/src/LightGeneralizedTCRMapping.ts +++ b/src/LightGeneralizedTCRMapping.ts @@ -1,17 +1,12 @@ /* eslint-disable prefer-const */ import { - Bytes, BigInt, Address, - ipfs, - json, - JSONValue, - JSONValueKind, log, + DataSourceContext, } from '@graphprotocol/graph-ts'; import { LItem, - ItemProp, LRequest, LRound, LRegistry, @@ -26,7 +21,11 @@ import { AppealDecision, IArbitrator, } from '../generated/templates/LIArbitrator/IArbitrator'; -import { LIArbitrator as IArbitratorDataSourceTemplate } from '../generated/templates'; +import { + LIArbitrator as IArbitratorDataSourceTemplate, + LGTCREvidence as EvidenceMetadataTemplate, + LItemMetadata as LItemMetadataTemplate, +} from '../generated/templates'; import { Contribution, Dispute, @@ -40,6 +39,7 @@ import { Ruling, ConnectedTCRSet as ConnectedTCRSetEvent, } from '../generated/templates/LightGeneralizedTCR/LightGeneralizedTCR'; +import { ZERO_ADDRESS, extractPath } from './utils'; // Items on a TCR can be in 1 of 4 states: // - (0) Absent: The item is not registered on the TCR and there are no pending requests. @@ -180,21 +180,17 @@ function updateCounters( BigInt.fromI32(1), ); } else if (previousStatus == REGISTRATION_REQUESTED_CODE) { - registry.numberOfRegistrationRequested = registry.numberOfRegistrationRequested.minus( - BigInt.fromI32(1), - ); + registry.numberOfRegistrationRequested = + registry.numberOfRegistrationRequested.minus(BigInt.fromI32(1)); } else if (previousStatus == CLEARING_REQUESTED_CODE) { - registry.numberOfClearingRequested = registry.numberOfClearingRequested.minus( - BigInt.fromI32(1), - ); + registry.numberOfClearingRequested = + registry.numberOfClearingRequested.minus(BigInt.fromI32(1)); } else if (previousStatus == CHALLENGED_REGISTRATION_REQUEST_CODE) { - registry.numberOfChallengedRegistrations = registry.numberOfChallengedRegistrations.minus( - BigInt.fromI32(1), - ); + registry.numberOfChallengedRegistrations = + registry.numberOfChallengedRegistrations.minus(BigInt.fromI32(1)); } else if (previousStatus == CHALLENGED_CLEARING_REQUEST_CODE) { - registry.numberOfChallengedClearing = registry.numberOfChallengedClearing.minus( - BigInt.fromI32(1), - ); + registry.numberOfChallengedClearing = + registry.numberOfChallengedClearing.minus(BigInt.fromI32(1)); } if (newStatus == ABSENT_CODE) { @@ -204,78 +200,22 @@ function updateCounters( BigInt.fromI32(1), ); } else if (newStatus == REGISTRATION_REQUESTED_CODE) { - registry.numberOfRegistrationRequested = registry.numberOfRegistrationRequested.plus( - BigInt.fromI32(1), - ); + registry.numberOfRegistrationRequested = + registry.numberOfRegistrationRequested.plus(BigInt.fromI32(1)); } else if (newStatus == CLEARING_REQUESTED_CODE) { - registry.numberOfClearingRequested = registry.numberOfClearingRequested.plus( - BigInt.fromI32(1), - ); + registry.numberOfClearingRequested = + registry.numberOfClearingRequested.plus(BigInt.fromI32(1)); } else if (newStatus == CHALLENGED_REGISTRATION_REQUEST_CODE) { - registry.numberOfChallengedRegistrations = registry.numberOfChallengedRegistrations.plus( - BigInt.fromI32(1), - ); + registry.numberOfChallengedRegistrations = + registry.numberOfChallengedRegistrations.plus(BigInt.fromI32(1)); } else if (newStatus == CHALLENGED_CLEARING_REQUEST_CODE) { - registry.numberOfChallengedClearing = registry.numberOfChallengedClearing.plus( - BigInt.fromI32(1), - ); + registry.numberOfChallengedClearing = + registry.numberOfChallengedClearing.plus(BigInt.fromI32(1)); } registry.save(); } -let ZERO_ADDRESS = Bytes.fromHexString( - '0x0000000000000000000000000000000000000000', -) as Bytes; - -function JSONValueToMaybeString( - value: JSONValue | null, - _default: string = '-', -): string { - // Subgraph considers an empty string to be null and - // the handler crashes when attempting to save the entity. - // This is a security vulnerability because an adversary - // could manually craft an item with missing columns - // and the item would not show up in the UI, passing - // the challenge period unoticed. - // - // We fix this by setting the field manually to a hifen. - if (value == null || value.isNull()) { - return '-'; - } - - switch (value.kind) { - case JSONValueKind.BOOL: - return value.toBool() == true ? 'true' : 'false'; - case JSONValueKind.STRING: - return value.toString(); - case JSONValueKind.NUMBER: - return value.toBigInt().toHexString(); - default: - return _default; - } -} - -function JSONValueToBool( - value: JSONValue | null, - _default: boolean = false, -): boolean { - if (value == null || value.isNull()) { - return _default; - } - - switch (value.kind) { - case JSONValueKind.BOOL: - return value.toBool(); - case JSONValueKind.STRING: - return value.toString() == 'true'; - case JSONValueKind.NUMBER: - return value.toBigInt().notEqual(BigInt.fromString('0')); - default: - return _default; - } -} - export function handleNewItem(event: NewItem): void { // We assume this is an item added via addItemDirectly and care // only about saving the item json data. @@ -309,98 +249,14 @@ export function handleNewItem(event: NewItem): void { item.latestRequestResolutionTime = BigInt.fromI32(0); item.latestRequestSubmissionTime = BigInt.fromI32(0); - item.keywords = event.address.toHexString(); - - // Offchain item data could be unavailable. We cannot let - // this handler fail otherwise an item would pass the challenge - // period unnoticed. Instead we set dummy data so challengers - // have a chance to check this. - let jsonStr = ipfs.cat(item.data); - if (!jsonStr) { - log.error('Failed to fetch item #{} JSON: {}', [graphItemID, item.data]); - item.save(); - registry.save(); - return; - } - - let jsonObjValueAndSuccess = json.try_fromBytes(jsonStr as Bytes); - if (!jsonObjValueAndSuccess.isOk) { - log.error(`Error getting json object value for graphItemID {}`, [ - graphItemID, - ]); - item.save(); - registry.save(); - return; - } - - let jsonObj = jsonObjValueAndSuccess.value.toObject(); - if (!jsonObj) { - log.error(`Error converting object for graphItemID {}`, [graphItemID]); - item.save(); - registry.save(); - return; - } - - let columnsValue = jsonObj.get('columns'); - if (!columnsValue) { - log.error(`Error getting column values for graphItemID {}`, [graphItemID]); - item.save(); - registry.save(); - return; - } - let columns = columnsValue.toArray(); - - let valuesValue = jsonObj.get('values'); - if (!valuesValue) { - log.error(`Error getting valuesValue for graphItemID {}`, [graphItemID]); - item.save(); - registry.save(); - return; - } - let values = valuesValue.toObject(); - - let identifier = 0; - for (let i = 0; i < columns.length; i++) { - let col = columns[i]; - let colObj = col.toObject(); - - let label = colObj.get('label'); - - // We must account for items with missing fields. - let checkedLabel = label - ? label.toString() - : 'missing-label'.concat(i.toString()); - - let description = colObj.get('description'); - let _type = colObj.get('type'); - let isIdentifier = colObj.get('isIdentifier'); - let value = values.get(checkedLabel); - let itemPropId = graphItemID + '@' + checkedLabel; - let itemProp = new ItemProp(itemPropId); - - itemProp.value = JSONValueToMaybeString(value); - itemProp.type = JSONValueToMaybeString(_type); - itemProp.label = JSONValueToMaybeString(label); - itemProp.description = JSONValueToMaybeString(description); - itemProp.isIdentifier = JSONValueToBool(isIdentifier); - itemProp.item = item.id; - - if (itemProp.isIdentifier) { - if (identifier == 0) item.key0 = itemProp.value; - else if (identifier == 1) item.key1 = itemProp.value; - else if (identifier == 2) item.key2 = itemProp.value; - else if (identifier == 3) item.key3 = itemProp.value; - else if (identifier == 4) item.key4 = itemProp.value; - identifier += 1; - } + const ipfsHash = extractPath(event.params._data); + item.metadata = ipfsHash; - if (itemProp.isIdentifier && itemProp.value != null && item.keywords) { - item.keywords = - (item.keywords as string) + ' | ' + (itemProp.value as string); - } + const context = new DataSourceContext(); + context.setString('graphItemID', graphItemID); + context.setString('address', event.address.toHexString()); - itemProp.save(); - } + LItemMetadataTemplate.createWithContext(ipfsHash, context); item.save(); registry.save(); @@ -493,9 +349,8 @@ export function handleRequestSubmitted(event: RequestSubmitted): void { if (itemInfo.value1.equals(BigInt.fromI32(1))) { // This is the first request for this item, which must be // a registration request. - registry.numberOfRegistrationRequested = registry.numberOfRegistrationRequested.plus( - BigInt.fromI32(1), - ); + registry.numberOfRegistrationRequested = + registry.numberOfRegistrationRequested.plus(BigInt.fromI32(1)); } else { updateCounters(previousStatus, newStatus, event.address); } @@ -992,80 +847,9 @@ export function handleEvidence(event: EvidenceEvent): void { BigInt.fromI32(1), ); - // Try to parse and store evidence fields. - let jsonStr = ipfs.cat(event.params._evidence); - if (!jsonStr) { - log.warning('Failed to fetch evidence {}', [event.params._evidence]); - evidenceGroup.save(); - evidence.save(); - return; - } - - let jsonObjValueAndSuccess = json.try_fromBytes(jsonStr as Bytes); - if (!jsonObjValueAndSuccess.isOk) { - log.warning(`Error getting json object value for evidence {}`, [ - event.params._evidence, - ]); - evidenceGroup.save(); - evidence.save(); - return; - } - - let jsonObj = jsonObjValueAndSuccess.value.toObject(); - if (!jsonObj) { - log.warning(`Error converting object for evidence {}`, [ - event.params._evidence, - ]); - evidenceGroup.save(); - evidence.save(); - return; - } - - let nameValue = jsonObj.get('name'); - if (!nameValue) { - log.warning(`Error getting name value for evidence {}`, [ - event.params._evidence, - ]); - } else { - evidence.name = nameValue.toString(); - } - - // Somehow Curate uses "title"?? so fetch in case - let titleValue = jsonObj.get('title'); - if (!titleValue) { - log.error(`Error getting title value for evidence {}`, [ - event.params._evidence, - ]); - } else { - evidence.title = titleValue.toString(); - } - - let descriptionValue = jsonObj.get('description'); - if (!descriptionValue) { - log.warning(`Error getting description value for evidence {}`, [ - event.params._evidence, - ]); - } else { - evidence.description = descriptionValue.toString(); - } - - let fileURIValue = jsonObj.get('fileURI'); - if (!fileURIValue) { - log.warning(`Error getting fileURI value for evidence {}`, [ - event.params._evidence, - ]); - } else { - evidence.fileURI = fileURIValue.toString(); - } - - let fileTypeExtensionValue = jsonObj.get('fileTypeExtension'); - if (!fileTypeExtensionValue) { - log.warning(`Error getting fileTypeExtension value for evidence {}`, [ - event.params._evidence, - ]); - } else { - evidence.fileTypeExtension = fileTypeExtensionValue.toString(); - } + const ipfsHash = extractPath(event.params._evidence); + evidence.metadata = ipfsHash; + EvidenceMetadataTemplate.create(ipfsHash); evidenceGroup.save(); evidence.save(); diff --git a/src/fileHandlers/EvidenceMetadataHandler.ts b/src/fileHandlers/EvidenceMetadataHandler.ts new file mode 100644 index 0000000..b214adb --- /dev/null +++ b/src/fileHandlers/EvidenceMetadataHandler.ts @@ -0,0 +1,53 @@ +import { Bytes, dataSource, json, log } from '@graphprotocol/graph-ts'; +import { EvidenceMetadata } from '../../generated/schema'; + +export function handleGTCREvidenceMetadata(content: Bytes): void { + const id = dataSource.stringParam(); + const evidence = new EvidenceMetadata(id); + const value = json.fromBytes(content).toObject(); + + log.debug(`ipfs hash : {}, content : {}`, [id, content.toString()]); + + if (!value) { + log.warning(`Error converting object for evidence {}`, [id]); + evidence.save(); + return; + } + + const nameValue = value.get('name'); + if (!nameValue) { + log.warning(`Error getting name value for evidence {}`, [id]); + } else { + evidence.name = nameValue.toString(); + } + + const titleValue = value.get('title'); + if (!titleValue) { + log.warning(`Error getting title value for evidence {}`, [id]); + } else { + evidence.title = titleValue.toString(); + } + + const descriptionValue = value.get('description'); + if (!descriptionValue) { + log.warning(`Error getting description value for evidence {}`, [id]); + } else { + evidence.description = descriptionValue.toString(); + } + + const fileURIValue = value.get('fileURI'); + if (!fileURIValue) { + log.warning(`Error getting fileURI value for evidence {}`, [id]); + } else { + evidence.fileURI = fileURIValue.toString(); + } + + const fileTypeExtensionValue = value.get('fileTypeExtension'); + if (!fileTypeExtensionValue) { + log.warning(`Error getting fileTypeExtension value for evidence {}`, [id]); + } else { + evidence.fileTypeExtension = fileTypeExtensionValue.toString(); + } + + evidence.save(); +} diff --git a/src/fileHandlers/LEvidenceMetadataHandler.ts b/src/fileHandlers/LEvidenceMetadataHandler.ts new file mode 100644 index 0000000..77ad980 --- /dev/null +++ b/src/fileHandlers/LEvidenceMetadataHandler.ts @@ -0,0 +1,53 @@ +import { Bytes, dataSource, json, log } from '@graphprotocol/graph-ts'; +import { EvidenceMetadata } from '../../generated/schema'; + +export function handleLGTCREvidenceMetadata(content: Bytes): void { + const id = dataSource.stringParam(); + const evidence = new EvidenceMetadata(id); + const value = json.fromBytes(content).toObject(); + + log.debug(`ipfs hash : {}, content : {}`, [id, content.toString()]); + + if (!value) { + log.warning(`Error converting object for evidence {}`, [id]); + evidence.save(); + return; + } + + const nameValue = value.get('name'); + if (!nameValue) { + log.warning(`Error getting name value for evidence {}`, [id]); + } else { + evidence.name = nameValue.toString(); + } + + const titleValue = value.get('title'); + if (!titleValue) { + log.warning(`Error getting title value for evidence {}`, [id]); + } else { + evidence.title = titleValue.toString(); + } + + const descriptionValue = value.get('description'); + if (!descriptionValue) { + log.warning(`Error getting description value for evidence {}`, [id]); + } else { + evidence.description = descriptionValue.toString(); + } + + const fileURIValue = value.get('fileURI'); + if (!fileURIValue) { + log.warning(`Error getting fileURI value for evidence {}`, [id]); + } else { + evidence.fileURI = fileURIValue.toString(); + } + + const fileTypeExtensionValue = value.get('fileTypeExtension'); + if (!fileTypeExtensionValue) { + log.warning(`Error getting fileTypeExtension value for evidence {}`, [id]); + } else { + evidence.fileTypeExtension = fileTypeExtensionValue.toString(); + } + + evidence.save(); +} diff --git a/src/fileHandlers/LItemMetadataHandler.ts b/src/fileHandlers/LItemMetadataHandler.ts new file mode 100644 index 0000000..449240f --- /dev/null +++ b/src/fileHandlers/LItemMetadataHandler.ts @@ -0,0 +1,84 @@ +import { Bytes, dataSource, json, log } from '@graphprotocol/graph-ts'; +import { ItemProp, LItemMetadata } from '../../generated/schema'; +import { JSONValueToBool, JSONValueToMaybeString } from '../utils'; + +export function handleLItemMetadata(content: Bytes): void { + const id = dataSource.stringParam(); + const metadata = new LItemMetadata(id); + + const value = json.fromBytes(content).toObject(); + const context = dataSource.context(); + + const graphItemID = context.getString('graphItemID'); + const address = context.getString('address'); + + metadata.keywords = address; + + log.debug(`ipfs hash : {}, content : {}`, [id, content.toString()]); + + if (!value) { + log.warning(`Error converting object for graphItemId {}`, [graphItemID]); + metadata.save(); + return; + } + + const columnsValue = value.get('columns'); + if (!columnsValue) { + log.error(`Error getting column values for graphItemID {}`, [graphItemID]); + metadata.save(); + return; + } + const columns = columnsValue.toArray(); + + const valuesValue = value.get('values'); + if (!valuesValue) { + log.error(`Error getting valuesValue for graphItemID {}`, [graphItemID]); + metadata.save(); + return; + } + const values = valuesValue.toObject(); + + let identifier = 0; + for (let i = 0; i < columns.length; i++) { + const col = columns[i]; + const colObj = col.toObject(); + + const label = colObj.get('label'); + + // We must account for items with missing fields. + const checkedLabel = label + ? label.toString() + : 'missing-label'.concat(i.toString()); + + const description = colObj.get('description'); + const _type = colObj.get('type'); + const isIdentifier = colObj.get('isIdentifier'); + const value = values.get(checkedLabel); + const itemPropId = graphItemID + '@' + checkedLabel; + const itemProp = new ItemProp(itemPropId); + + itemProp.value = JSONValueToMaybeString(value); + itemProp.type = JSONValueToMaybeString(_type); + itemProp.label = JSONValueToMaybeString(label); + itemProp.description = JSONValueToMaybeString(description); + itemProp.isIdentifier = JSONValueToBool(isIdentifier); + itemProp.item = id; + + if (itemProp.isIdentifier) { + if (identifier == 0) metadata.key0 = itemProp.value; + else if (identifier == 1) metadata.key1 = itemProp.value; + else if (identifier == 2) metadata.key2 = itemProp.value; + else if (identifier == 3) metadata.key3 = itemProp.value; + else if (identifier == 4) metadata.key4 = itemProp.value; + identifier += 1; + } + + if (itemProp.isIdentifier && itemProp.value != null && metadata.keywords) { + metadata.keywords = + (metadata.keywords as string) + ' | ' + (itemProp.value as string); + } + + itemProp.save(); + } + metadata.save(); +} diff --git a/src/utils/index.ts b/src/utils/index.ts new file mode 100644 index 0000000..49f788a --- /dev/null +++ b/src/utils/index.ts @@ -0,0 +1,77 @@ +import { + BigInt, + Bytes, + JSONValue, + JSONValueKind, +} from '@graphprotocol/graph-ts'; + +/** + * @description extracts the cid from ipfs strings starting with "/", "/ipfs", "ipfs/", or "ipfs::/" + * @param inputString the ipfs string + * @returns returns the cid with path to file + */ +export function extractPath(inputString: string): string { + if (inputString.startsWith('ipfs/')) return inputString.replace('ipfs/', ''); + + if (inputString.startsWith('/ipfs/')) + return inputString.replace('/ipfs/', ''); + + if (inputString.startsWith('/')) return inputString.replace('/', ''); + + if (inputString.startsWith('ipfs::/')) + return inputString.replace('ipfs::/', ''); + + return inputString; +} + +export const ZERO_ADDRESS = Bytes.fromHexString( + '0x0000000000000000000000000000000000000000', +) as Bytes; + +export function JSONValueToMaybeString( + value: JSONValue | null, + _default: string = '-', +): string { + // Subgraph considers an empty string to be null and + // the handler crashes when attempting to save the entity. + // This is a security vulnerability because an adversary + // could manually craft an item with missing columns + // and the item would not show up in the UI, passing + // the challenge period unoticed. + // + // We fix this by setting the field manually to a hifen. + if (value == null || value.isNull()) { + return '-'; + } + + switch (value.kind) { + case JSONValueKind.BOOL: + return value.toBool() == true ? 'true' : 'false'; + case JSONValueKind.STRING: + return value.toString(); + case JSONValueKind.NUMBER: + return value.toBigInt().toHexString(); + default: + return _default; + } +} + +export function JSONValueToBool( + value: JSONValue | null, + _default: boolean = false, +): boolean { + if (value == null || value.isNull()) { + return _default; + } + + switch (value.kind) { + case JSONValueKind.BOOL: + return value.toBool(); + case JSONValueKind.STRING: + return value.toString() == 'true'; + case JSONValueKind.NUMBER: + return value.toBigInt().notEqual(BigInt.fromString('0')); + default: + return _default; + } +} diff --git a/subgraph.template.yaml b/subgraph.template.yaml index b0fb6d5..a4ccbf2 100644 --- a/subgraph.template.yaml +++ b/subgraph.template.yaml @@ -16,7 +16,7 @@ dataSources: startBlock: {{LightGTCRFactory.startBlock}} mapping: kind: ethereum/events - apiVersion: 0.0.5 + apiVersion: 0.0.7 language: wasm/assemblyscript entities: - MetaEvidence @@ -37,7 +37,7 @@ dataSources: startBlock: {{GTCRFactory.startBlock}} mapping: kind: ethereum/events - apiVersion: 0.0.5 + apiVersion: 0.0.7 language: wasm/assemblyscript entities: - MetaEvidence @@ -57,7 +57,7 @@ templates: abi: LightGeneralizedTCR mapping: kind: ethereum/events - apiVersion: 0.0.5 + apiVersion: 0.0.7 language: wasm/assemblyscript entities: - LItem @@ -103,7 +103,7 @@ templates: abi: IArbitrator mapping: kind: ethereum/events - apiVersion: 0.0.5 + apiVersion: 0.0.7 language: wasm/assemblyscript entities: - Arbitrator @@ -125,7 +125,7 @@ templates: abi: GeneralizedTCR mapping: kind: ethereum/events - apiVersion: 0.0.5 + apiVersion: 0.0.7 language: wasm/assemblyscript entities: - Item @@ -166,7 +166,7 @@ templates: abi: IArbitrator mapping: kind: ethereum/events - apiVersion: 0.0.5 + apiVersion: 0.0.7 language: wasm/assemblyscript entities: - Item @@ -186,3 +186,40 @@ templates: - event: AppealDecision(indexed uint256,indexed address) handler: handleAppealDecision file: ./src/GeneralizedTCRMapping.ts + - name: GTCREvidenceMetadata + kind: file/ipfs + mapping: + apiVersion: 0.0.7 + language: wasm/assemblyscript + file: ./src/fileHandlers/EvidenceMetadataHandler.ts + handler: handleGTCREvidenceMetadata + entities: + - EvidenceMetadata + abis: + - name: GeneralizedTCR + file: ./abis/GeneralizedTCR.json + - name: LGTCREvidence + kind: file/ipfs + mapping: + apiVersion: 0.0.7 + language: wasm/assemblyscript + file: ./src/fileHandlers/LEvidenceMetadataHandler.ts + handler: handleLGTCREvidenceMetadata + entities: + - EvidenceMetadata + abis: + - name: LightGeneralizedTCR + file: ./abis/LightGeneralizedTCR.json + - name: LItemMetadata + kind: file/ipfs + mapping: + apiVersion: 0.0.7 + language: wasm/assemblyscript + file: ./src/fileHandlers/LItemMetadataHandler.ts + handler: handleLItemMetadata + entities: + - LItemMetadata + - ItemProp + abis: + - name: LightGeneralizedTCR + file: ./abis/LightGeneralizedTCR.json \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index c822cb0..2247e97 100644 --- a/yarn.lock +++ b/yarn.lock @@ -597,13 +597,15 @@ walk "^2.3.14" web3 "^1.0.0-beta.35" -"@graphprotocol/graph-cli@^0.54.0": - version "0.54.0" - resolved "https://registry.yarnpkg.com/@graphprotocol/graph-cli/-/graph-cli-0.54.0.tgz#78c85423e284f6d24e781b66dadabe7ee43a9244" - integrity sha512-Q9dJZgjNHToJ+6DqoxU9WPwITjN6EHoONh05z5/atzrj1QTNqWQT0TYrVGX/Lvm9eqa6R+Knof1jiseBLJ43tg== +"@graphprotocol/graph-cli@^0.71.0": + version "0.71.0" + resolved "https://registry.yarnpkg.com/@graphprotocol/graph-cli/-/graph-cli-0.71.0.tgz#1ae67f0423793189406eacfb07c80f74cba37e6e" + integrity sha512-ITcSBHuXPuaoRs7FzNtqD0tCOIy4JGsM3j4IQNA2yZgXgr/TmmHG7KTB/c3B5Zlnsr9foXrU71T6ixGmwJ4PKw== dependencies: "@float-capital/float-subgraph-uncrashable" "^0.0.0-alpha.4" "@oclif/core" "2.8.6" + "@oclif/plugin-autocomplete" "^2.3.6" + "@oclif/plugin-not-found" "^2.4.0" "@whatwg-node/fetch" "^0.8.4" assemblyscript "0.19.23" binary-install-raw "0.0.13" @@ -614,14 +616,13 @@ dockerode "2.5.8" fs-extra "9.1.0" glob "9.3.5" - gluegun "5.1.2" + gluegun "5.1.6" graphql "15.5.0" immutable "4.2.1" ipfs-http-client "55.0.0" jayson "4.0.0" js-yaml "3.14.1" - prettier "1.19.1" - request "2.88.2" + prettier "3.0.3" semver "7.4.0" sync-request "6.1.0" tmp-promise "3.0.3" @@ -629,10 +630,10 @@ which "2.0.2" yaml "1.10.2" -"@graphprotocol/graph-ts@^0.31.0": - version "0.31.0" - resolved "https://registry.yarnpkg.com/@graphprotocol/graph-ts/-/graph-ts-0.31.0.tgz#730668c0369828b31bef81e8d9bc66b9b48e3480" - integrity sha512-xreRVM6ho2BtolyOh2flDkNoGZximybnzUnF53zJVp0+Ed0KnAlO1/KOCUYw06euVI9tk0c9nA2Z/D5SIQV2Rg== +"@graphprotocol/graph-ts@^0.35.1": + version "0.35.1" + resolved "https://registry.yarnpkg.com/@graphprotocol/graph-ts/-/graph-ts-0.35.1.tgz#1e1ecc36d8f7a727ef3a6f1fed4c5ce16de378c2" + integrity sha512-74CfuQmf7JI76/XCC34FTkMMKeaf+3Pn0FIV3m9KNeaOJ+OI3CvjMIVRhOZdKcJxsFCBGaCCl0eQjh47xTjxKA== dependencies: assemblyscript "0.19.10" @@ -785,6 +786,58 @@ wordwrap "^1.0.0" wrap-ansi "^7.0.0" +"@oclif/core@^2.15.0": + version "2.16.0" + resolved "https://registry.yarnpkg.com/@oclif/core/-/core-2.16.0.tgz#e6f3c6c359d4313a15403d8652bbdd0e99ce4b3a" + integrity sha512-dL6atBH0zCZl1A1IXCKJgLPrM/wR7K+Wi401E/IvqsK8m2iCHW+0TEOGrans/cuN3oTW+uxIyJFHJ8Im0k4qBw== + dependencies: + "@types/cli-progress" "^3.11.0" + ansi-escapes "^4.3.2" + ansi-styles "^4.3.0" + cardinal "^2.1.1" + chalk "^4.1.2" + clean-stack "^3.0.1" + cli-progress "^3.12.0" + debug "^4.3.4" + ejs "^3.1.8" + get-package-type "^0.1.0" + globby "^11.1.0" + hyperlinker "^1.0.0" + indent-string "^4.0.0" + is-wsl "^2.2.0" + js-yaml "^3.14.1" + natural-orderby "^2.0.3" + object-treeify "^1.1.33" + password-prompt "^1.1.2" + slice-ansi "^4.0.0" + string-width "^4.2.3" + strip-ansi "^6.0.1" + supports-color "^8.1.1" + supports-hyperlinks "^2.2.0" + ts-node "^10.9.1" + tslib "^2.5.0" + widest-line "^3.1.0" + wordwrap "^1.0.0" + wrap-ansi "^7.0.0" + +"@oclif/plugin-autocomplete@^2.3.6": + version "2.3.10" + resolved "https://registry.yarnpkg.com/@oclif/plugin-autocomplete/-/plugin-autocomplete-2.3.10.tgz#787f6208cdfe10ffc68ad89e9e7f1a7ad0e8987f" + integrity sha512-Ow1AR8WtjzlyCtiWWPgzMyT8SbcDJFr47009riLioHa+MHX2BCDtVn2DVnN/E6b9JlPV5ptQpjefoRSNWBesmg== + dependencies: + "@oclif/core" "^2.15.0" + chalk "^4.1.0" + debug "^4.3.4" + +"@oclif/plugin-not-found@^2.4.0": + version "2.4.3" + resolved "https://registry.yarnpkg.com/@oclif/plugin-not-found/-/plugin-not-found-2.4.3.tgz#3d24095adb0f3876cb4bcfdfdcb775086cf6d4b5" + integrity sha512-nIyaR4y692frwh7wIHZ3fb+2L6XEecQwRDIb4zbEam0TvaVmBQWZoColQyWA84ljFBPZ8XWiQyTz+ixSwdRkqg== + dependencies: + "@oclif/core" "^2.15.0" + chalk "^4" + fast-levenshtein "^3.0.0" + "@peculiar/asn1-schema@^2.3.6": version "2.3.6" resolved "https://registry.yarnpkg.com/@peculiar/asn1-schema/-/asn1-schema-2.3.6.tgz#3dd3c2ade7f702a9a94dfb395c192f5fa5d6b922" @@ -2315,7 +2368,7 @@ chalk@^2.0.0, chalk@^2.0.1, chalk@^2.3.2, chalk@^2.4.1, chalk@^2.4.2: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.2: +chalk@^4, chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -3179,12 +3232,12 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== -ejs@3.1.6: - version "3.1.6" - resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.6.tgz#5bfd0a0689743bb5268b3550cceeebbc1702822a" - integrity sha512-9lt9Zse4hPucPkoP7FHDF0LQAlGyF9JVpnClFLFH3aSSbxmyoqINRpp/9wePWJTUl4KOQwRL72Iw3InHPDkoGw== +ejs@3.1.8: + version "3.1.8" + resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.8.tgz#758d32910c78047585c7ef1f92f9ee041c1c190b" + integrity sha512-/sXZeMlhS0ArkfX2Aw780gJzXSMPnKjtspYZv+f3NiKLlubezAHDU5+9xz6gd3/NhG3txQCo6xlglmTS+oTGEQ== dependencies: - jake "^10.6.1" + jake "^10.8.5" ejs@^3.1.8: version "3.1.9" @@ -4066,6 +4119,13 @@ fast-levenshtein@^2.0.6: resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== +fast-levenshtein@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-3.0.0.tgz#37b899ae47e1090e40e3fd2318e4d5f0142ca912" + integrity sha512-hKKNajm46uNmTlhHSyZkmToAc56uZJwYq7yrciZjqOxnlfQwERDQJmHPUp7m1m9wx8vgOe8IaCKZ5Kv2k1DdCQ== + dependencies: + fastest-levenshtein "^1.0.7" + fast-querystring@^1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/fast-querystring/-/fast-querystring-1.1.2.tgz#a6d24937b4fc6f791b4ee31dcb6f53aeafb89f53" @@ -4085,6 +4145,11 @@ fast-url-parser@^1.1.3: dependencies: punycode "^1.3.2" +fastest-levenshtein@^1.0.7: + version "1.0.16" + resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz#210e61b6ff181de91ea9b3d1b84fdedd47e034e5" + integrity sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg== + fastq@^1.6.0: version "1.15.0" resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.15.0.tgz#d04d07c6a2a68fe4599fea8d2e103a937fae6b3a" @@ -4507,10 +4572,10 @@ globby@^11.0.3, globby@^11.1.0: merge2 "^1.4.1" slash "^3.0.0" -gluegun@5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/gluegun/-/gluegun-5.1.2.tgz#ffa0beda0fb6bbc089a867157b08602beae2c8cf" - integrity sha512-Cwx/8S8Z4YQg07a6AFsaGnnnmd8mN17414NcPS3OoDtZRwxgsvwRNJNg69niD6fDa8oNwslCG0xH7rEpRNNE/g== +gluegun@5.1.6: + version "5.1.6" + resolved "https://registry.yarnpkg.com/gluegun/-/gluegun-5.1.6.tgz#74ec13193913dc610f5c1a4039972c70c96a7bad" + integrity sha512-9zbi4EQWIVvSOftJWquWzr9gLX2kaDgPkNR5dYWbM53eVvCI3iKuxLlnKoHC0v4uPoq+Kr/+F569tjoFbA4DSA== dependencies: apisauce "^2.1.5" app-module-path "^2.2.0" @@ -4518,7 +4583,7 @@ gluegun@5.1.2: colors "1.4.0" cosmiconfig "7.0.1" cross-spawn "7.0.3" - ejs "3.1.6" + ejs "3.1.8" enquirer "2.3.6" execa "5.1.1" fs-jetpack "4.3.1" @@ -5426,7 +5491,7 @@ it-to-stream@^1.0.0: p-fifo "^1.0.0" readable-stream "^3.6.0" -jake@^10.6.1, jake@^10.8.5: +jake@^10.8.5: version "10.8.7" resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.7.tgz#63a32821177940c33f356e0ba44ff9d34e1c7d8f" integrity sha512-ZDi3aP+fG/LchyBzUM804VjddnwfSfsdeYkwt8NcbKRvo4rFkjhs456iLFn3k2ZUWvNe4i48WACDbza8fhq2+w== @@ -6928,10 +6993,10 @@ prettier-linter-helpers@^1.0.0: dependencies: fast-diff "^1.1.2" -prettier@1.19.1: - version "1.19.1" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb" - integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew== +prettier@3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.0.3.tgz#432a51f7ba422d1469096c0fdc28e235db8f9643" + integrity sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg== process-nextick-args@~2.0.0: version "2.0.1" @@ -7265,7 +7330,7 @@ replace-in-file@^6.1.0: glob "^7.2.0" yargs "^17.2.1" -request@2.88.2, request@^2.67.0, request@^2.79.0, request@^2.85.0: +request@^2.67.0, request@^2.79.0, request@^2.85.0: version "2.88.2" resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==