From 01cceeac03f719df6eb8be8c238aca4483e7a3e8 Mon Sep 17 00:00:00 2001 From: Andreas Richter <708186+richtera@users.noreply.github.com> Date: Tue, 16 Jan 2024 15:30:53 -0500 Subject: [PATCH] fix: Repair tuples containing numeric types uintX/intX, add Number to output data type. --- src/index.test.ts | 87 ++++++++++++++++++++++++++ src/index.ts | 4 +- src/lib/decodeData.ts | 4 ++ src/lib/encoder.ts | 5 +- src/lib/getDataFromExternalSources.ts | 90 +++++++++++++++------------ src/types/decodeData.ts | 4 +- 6 files changed, 150 insertions(+), 44 deletions(-) diff --git a/src/index.test.ts b/src/index.test.ts index b8f34c85..8ce01ccd 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -341,6 +341,93 @@ describe('Running @erc725/erc725.js tests...', () => { }); }); + describe('By HttpProvider to retrieve single dynamic key with getDataBatch', () => { + const provider = new HttpProvider( + { + returnData: [ + { + key: '0x4b80742de2bf82acb36300009139def55c73c12bcda9c44f12326686e3948634', + value: + '0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002', + }, + ], + }, + [ERC725Y_INTERFACE_IDS['5.0']], + ); + + it('should return data even with a single BitArray key', async () => { + const erc725 = new ERC725( + [ + { + name: 'AddressPermissions:Permissions:
', + key: '0x4b80742de2bf82acb3630000
', + keyType: 'MappingWithGrouping', + valueType: 'bytes32', + valueContent: 'BitArray', + }, + ], + '0x24464DbA7e7781a21eD86133Ebe88Eb9C0762620', + provider, + ); + + const data = await erc725.getData([ + { + keyName: 'AddressPermissions:Permissions:
', + dynamicKeyParts: '0x9139def55c73c12bcda9c44f12326686e3948634', + }, + ]); + assert.deepStrictEqual(data[0], { + key: '0x4b80742de2bf82acb36300009139def55c73c12bcda9c44f12326686e3948634', + name: 'AddressPermissions:Permissions:9139def55c73c12bcda9c44f12326686e3948634', + value: + '0x0000000000000000000000000000000000000000000000000000000000000002', + }); + }); + }); + + describe('By HttpProvider to retrieve single dynamic key with getDataBatch', () => { + const provider = new HttpProvider( + { + returnData: [ + { + key: '0x6de85eaf5d982b4e5da000009139def55c73c12bcda9c44f12326686e3948634', + value: + '0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001424871b3d00000000000000000000000000000000000000000000000000000000', + }, + ], + }, + [ERC725Y_INTERFACE_IDS['5.0']], + ); + + it('should return data even with a single BitArray key', async () => { + const erc725 = new ERC725( + [ + { + name: 'LSP4CreatorsMap:
', + key: '0x6de85eaf5d982b4e5da00000
', + keyType: 'Mapping', + valueType: '(bytes4,uint128)', + valueContent: '(Bytes4,Number)', + }, + ], + '0x24464DbA7e7781a21eD86133Ebe88Eb9C0762620', + provider, + ); + + const data = await erc725.getData([ + { + keyName: 'LSP4CreatorsMap:
', + dynamicKeyParts: '0x9139def55c73c12bcda9c44f12326686e3948634', + }, + ]); + assert.deepStrictEqual(data[0], { + key: '0x6de85eaf5d982b4e5da000009139def55c73c12bcda9c44f12326686e3948634', + name: 'LSP4CreatorsMap:9139def55c73c12bcda9c44f12326686e3948634', + value: ['0x24871b3d', 0], + }); + }); + }); + describe('By provider [e2e] - luksoTestnet', () => { const e2eSchema: ERC725JSONSchema[] = [ { diff --git a/src/index.ts b/src/index.ts index d75df5ae..d797aa50 100644 --- a/src/index.ts +++ b/src/index.ts @@ -252,7 +252,7 @@ export class ERC725 { keyOrKeys?: GetDataInput, ): Promise { let keyNames: Array; - + let throwException = false; if (Array.isArray(keyOrKeys)) { keyNames = keyOrKeys; } else if (!keyOrKeys) { @@ -260,6 +260,7 @@ export class ERC725 { .map((element) => element.name) .filter((key) => !isDynamicKeyName(key)); } else { + throwException = true; // If it's explicitely a single key, then we allow throwing an exception keyNames = [keyOrKeys]; } @@ -276,6 +277,7 @@ export class ERC725 { schemas, dataFromChain, this.options.ipfsGateway, + throwException, ); if ( diff --git a/src/lib/decodeData.ts b/src/lib/decodeData.ts index 7d676efa..360e35c1 100644 --- a/src/lib/decodeData.ts +++ b/src/lib/decodeData.ts @@ -150,6 +150,10 @@ export const decodeTupleKeyValue = ( // if we are dealing with `bytesN` if (regexMatch) bytesLengths.push(parseInt(regexMatch[1], 10)); + const numericMatch = valueTypePart.match(/u?int(\d+)/); + + if (numericMatch) bytesLengths.push(parseInt(numericMatch[1], 10) / 8); + if (valueTypePart === 'address') bytesLengths.push(20); }); diff --git a/src/lib/encoder.ts b/src/lib/encoder.ts index 9594329a..7092f3b6 100644 --- a/src/lib/encoder.ts +++ b/src/lib/encoder.ts @@ -795,7 +795,7 @@ export const valueContentEncodingMap = ( }, decode: (value: string) => { if (typeof value !== 'string' || !isHex(value)) { - console.log(`Value: ${value} is not hex.`); + console.error(`Value: ${value} is not hex.`); return null; } @@ -940,7 +940,8 @@ export function decodeValueContent( return valueContent === value ? value : null; } - if (!value || value === '0x') { + if (value == null || value === '0x') { + // !value allows 0 values to become null. return null; } diff --git a/src/lib/getDataFromExternalSources.ts b/src/lib/getDataFromExternalSources.ts index 1716bf11..3947bea6 100644 --- a/src/lib/getDataFromExternalSources.ts +++ b/src/lib/getDataFromExternalSources.ts @@ -26,11 +26,13 @@ import { import { ERC725JSONSchema } from '../types/ERC725JSONSchema'; import { SUPPORTED_VERIFICATION_METHOD_STRINGS } from '../constants/constants'; import { isDataAuthentic, patchIPFSUrlsIfApplicable } from './utils'; +import { URLDataWithHash } from '../types'; export const getDataFromExternalSources = ( schemas: ERC725JSONSchema[], dataFromChain: DecodeDataOutput[], ipfsGateway: string, + throwException = true, ): Promise => { const promises = dataFromChain.map(async (dataEntry) => { const schemaElement = schemas.find( @@ -51,53 +53,61 @@ export const getDataFromExternalSources = ( return dataEntry; } - // At this stage, value should be of type jsonurl, verifiableuri or asseturl - if (typeof dataEntry.value === 'string') { - console.error( - `Value of key: ${dataEntry.name} (${dataEntry.value}) is string but valueContent is: ${schemaElement.valueContent}. Expected type should be object with url key.`, - ); - return { ...dataEntry, value: null }; - } + try { + // At this stage, value should be of type jsonurl, verifiableuri or asseturl + if (typeof dataEntry.value === 'string') { + throw new Error( + `Value of key: ${dataEntry.name} (${dataEntry.value}) is string but valueContent is: ${schemaElement.valueContent}. Expected type should be object with url key.`, + ); + } - if (!dataEntry.value) { - return { ...dataEntry, value: null }; - } + if (!dataEntry.value) { + throw new Error(`Value of key: ${dataEntry.name} is empty`); + } - if (Array.isArray(dataEntry.value)) { - console.error( - `Value of key: ${dataEntry.name} (${dataEntry.value}) is string[] but valueContent is: ${schemaElement.valueContent}. Expected type should be object with url key.`, - ); - return { ...dataEntry, value: null }; - } + if (Array.isArray(dataEntry.value)) { + throw new Error( + `Value of key: ${dataEntry.name} (${dataEntry.value}) is string[] but valueContent is: ${schemaElement.valueContent}. Expected type should be object with url key.`, + ); + } - const urlDataWithHash = dataEntry.value; // Type URLDataWithHash + const urlDataWithHash: URLDataWithHash = + dataEntry.value as URLDataWithHash; // Type URLDataWithHash - let receivedData; - const { url } = patchIPFSUrlsIfApplicable(urlDataWithHash, ipfsGateway); - try { - receivedData = await fetch(url).then(async (response) => { - if (!response.ok) { - throw new Error(response.statusText); - } - if ( - urlDataWithHash.verification?.method === - SUPPORTED_VERIFICATION_METHOD_STRINGS.KECCAK256_BYTES - ) { - return response - .arrayBuffer() - .then((buffer) => new Uint8Array(buffer)); + let receivedData; + const { url } = patchIPFSUrlsIfApplicable( + urlDataWithHash as URLDataWithHash, + ipfsGateway, + ); + try { + receivedData = await fetch(url).then(async (response) => { + if (!response.ok) { + throw new Error(response.statusText); + } + if ( + urlDataWithHash.verification?.method === + SUPPORTED_VERIFICATION_METHOD_STRINGS.KECCAK256_BYTES + ) { + return response + .arrayBuffer() + .then((buffer) => new Uint8Array(buffer)); + } + return response.json(); + }); + if (isDataAuthentic(receivedData, urlDataWithHash.verification)) { + return { ...dataEntry, value: receivedData }; } - return response.json(); - }); - if (isDataAuthentic(receivedData, urlDataWithHash.verification)) { - return { ...dataEntry, value: receivedData }; + throw new Error('result did not correctly validate'); + } catch (error: any) { + error.message = `GET request to ${urlDataWithHash.url} (resolved as ${url}) failed: ${error.message}`; + throw error; } - throw new Error('result did not correctly validate'); } catch (error: any) { - console.error( - error, - `GET request to ${urlDataWithHash.url} (resolved as ${url})`, - ); + error.message = `Value of key: ${dataEntry.name} has an error: ${error.message}`; + if (throwException) { + throw error; + } + console.error(error); } // Invalid data return { ...dataEntry, value: null }; diff --git a/src/types/decodeData.ts b/src/types/decodeData.ts index e66e5ae6..b0e32342 100644 --- a/src/types/decodeData.ts +++ b/src/types/decodeData.ts @@ -14,8 +14,10 @@ export interface DecodeDataInput extends DataInput { value: string | { key: string; value: string | null }[]; } +export type Data = string | number | boolean | null; + export interface DecodeDataOutput { - value: string | string[] | URLDataWithHash | null; + value: Data | Data[] | URLDataWithHash | null; name: string; key: string; }