diff --git a/src/helpers.ts b/src/helpers.ts index ad342079..f31e26ec 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -177,10 +177,21 @@ export function arrayToJSONValue(value: string): JSONValue { // If byte data is parseable to a valid unicode string then do so // otherwise parse the byte data to a hex string export function bytesToJSONValue(value: Bytes): JSONValue { - // If the bytes cannot be - let result = json.try_fromString('["' + value.toString() + '"]'); - if (result.isError) { - result = json.try_fromString('["' + value.toHexString() + '"]'); + // fallback - assume the data is a hex string (always valid) + let result = json.try_fromString('["' + value.toHexString() + '"]'); + // If the bytes can be parsed as a string, then losslessly re-encoded into + // UTF-8 bytes, then consider a valid UTF-8 encoded string and store + // string value in json. + // note: Bytes.toString() uses WTF-8 encoding as opposed to UTF-8. Solidity + // encodes UTF-8 strings, so safe assume any string data are UTF-8 encoded. + let stringValue: string = value.toString(); + let reEncodedBytesValue: Bytes = Bytes.fromUTF8(stringValue); + if (reEncodedBytesValue.toHexString() == value.toHexString()) { + // if the bytes are the same then the string was valid UTF-8 + let potentialResult = json.try_fromString('["' + stringValue + '"]'); + if (potentialResult.isOk) { + result = potentialResult; + } } return result.value.toArray()[0]; } diff --git a/src/minter-suite-mapping.ts b/src/minter-suite-mapping.ts index 75bb58d5..8c110c92 100644 --- a/src/minter-suite-mapping.ts +++ b/src/minter-suite-mapping.ts @@ -727,7 +727,8 @@ export function handleAddManyValueGeneric( ) { stringVal = event.params._value.toHexString(); } else { - stringVal = event.params._value.toString(); + // for Bytes, use method to determine if string or hexString + stringVal = bytesToJSONValue(event.params._value).toString(); } arr.push(stringToJSONString(stringVal)); newValue = arrayToJSONValue(arr.toString()); diff --git a/tests/subgraph/minter-suite/minter-suite-mapping.test.ts b/tests/subgraph/minter-suite/minter-suite-mapping.test.ts index 2cb5d3ea..7503858b 100644 --- a/tests/subgraph/minter-suite/minter-suite-mapping.test.ts +++ b/tests/subgraph/minter-suite/minter-suite-mapping.test.ts @@ -1620,7 +1620,11 @@ test("handleSetValue should set all values to a designated key in extraMinterDet // If the bytes are not intended to be a human readable string // we should instead convert to their hex string representation - const eventValue = randomAddressGenerator.generateRandomAddress(); + // Always use the following hex string because valid WTF-8 but not valid + // UTF-8, which is a somewhat common edge-case when dealing with bytes + let eventValue = Bytes.fromHexString( + "0x57e32bd396b7c3337dd6b4a672e6d99b47865e56" + ); const configValueSetEvent4: ConfigValueSetBytes = changetype< ConfigValueSetBytes >(newMockEvent()); @@ -1820,13 +1824,36 @@ test("handleAddManyBytesValue should add a value to an array at a designated key '{"array":["im bytes"]}' ); - handleAddManyBytesValue(configValueSetEvent); + // add another value, this time with bytes that should format to hex string + const configValueSetEvent2: ConfigValueAddedToSetBytes = changetype< + ConfigValueAddedToSetBytes + >(newMockEvent()); + // Always use the following hex string because valid WTF-8 but not valid + // UTF-8, which is a somewhat common edge-case when dealing with bytes + let eventValue = Bytes.fromHexString( + "0x57e32bd396b7c3337dd6b4a672e6d99b47865e56" + ); + configValueSetEvent2.address = minterAddress; + configValueSetEvent2.parameters = [ + new ethereum.EventParam( + "_projectId", + ethereum.Value.fromUnsignedBigInt(projectId) + ), + new ethereum.EventParam( + "_key", + ethereum.Value.fromBytes(Bytes.fromUTF8("array")) + ), + new ethereum.EventParam("_value", ethereum.Value.fromBytes(eventValue)) + ]; + configValueSetEvent2.block.timestamp = CURRENT_BLOCK_TIMESTAMP; + + handleAddManyBytesValue(configValueSetEvent2); assert.fieldEquals( PROJECT_MINTER_CONFIGURATION_ENTITY_TYPE, getProjectMinterConfigId(minterAddress.toHexString(), project.id), "extraMinterDetails", - '{"array":["im bytes","im bytes"]}' + '{"array":["im bytes","0x57e32bd396b7c3337dd6b4a672e6d99b47865e56"]}' ); }); test("handleRemoveValue should remove the key/value from extraMinterDetails", () => {