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;
}