Skip to content

Commit

Permalink
fix: Repair tuples containing numeric types uintX/intX, add Number to…
Browse files Browse the repository at this point in the history
… output data type.
  • Loading branch information
richtera committed Jan 16, 2024
1 parent fde2e0f commit 01cceea
Show file tree
Hide file tree
Showing 6 changed files with 150 additions and 44 deletions.
87 changes: 87 additions & 0 deletions src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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:<address>',
key: '0x4b80742de2bf82acb3630000<address>',
keyType: 'MappingWithGrouping',
valueType: 'bytes32',
valueContent: 'BitArray',
},
],
'0x24464DbA7e7781a21eD86133Ebe88Eb9C0762620',
provider,
);

const data = await erc725.getData([
{
keyName: 'AddressPermissions:Permissions:<address>',
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:<address>',
key: '0x6de85eaf5d982b4e5da00000<address>',
keyType: 'Mapping',
valueType: '(bytes4,uint128)',
valueContent: '(Bytes4,Number)',
},
],
'0x24464DbA7e7781a21eD86133Ebe88Eb9C0762620',
provider,
);

const data = await erc725.getData([
{
keyName: 'LSP4CreatorsMap:<address>',
dynamicKeyParts: '0x9139def55c73c12bcda9c44f12326686e3948634',
},
]);
assert.deepStrictEqual(data[0], {
key: '0x6de85eaf5d982b4e5da000009139def55c73c12bcda9c44f12326686e3948634',
name: 'LSP4CreatorsMap:9139def55c73c12bcda9c44f12326686e3948634',
value: ['0x24871b3d', 0],
});
});
});

describe('By provider [e2e] - luksoTestnet', () => {
const e2eSchema: ERC725JSONSchema[] = [
{
Expand Down
4 changes: 3 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -252,14 +252,15 @@ export class ERC725 {
keyOrKeys?: GetDataInput,
): Promise<FetchDataOutput | FetchDataOutput[]> {
let keyNames: Array<string | GetDataDynamicKey>;

let throwException = false;
if (Array.isArray(keyOrKeys)) {
keyNames = keyOrKeys;
} else if (!keyOrKeys) {
keyNames = this.options.schemas
.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];
}

Expand All @@ -276,6 +277,7 @@ export class ERC725 {
schemas,
dataFromChain,
this.options.ipfsGateway,
throwException,
);

if (
Expand Down
4 changes: 4 additions & 0 deletions src/lib/decodeData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});

Expand Down
5 changes: 3 additions & 2 deletions src/lib/encoder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down Expand Up @@ -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;
}

Expand Down
90 changes: 50 additions & 40 deletions src/lib/getDataFromExternalSources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<GetDataExternalSourcesOutput[]> => {
const promises = dataFromChain.map(async (dataEntry) => {
const schemaElement = schemas.find(
Expand All @@ -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 };
Expand Down
4 changes: 3 additions & 1 deletion src/types/decodeData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down

0 comments on commit 01cceea

Please sign in to comment.