From c8148e050170874a7c7481bc9b47ae5d1c46424c Mon Sep 17 00:00:00 2001 From: Hugo Masclet Date: Wed, 29 Nov 2023 12:44:13 +0100 Subject: [PATCH 01/27] chore: run tests on node 18 and 20 --- .github/workflows/lint-test-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint-test-build.yml b/.github/workflows/lint-test-build.yml index 585c2266..8ff146d9 100644 --- a/.github/workflows/lint-test-build.yml +++ b/.github/workflows/lint-test-build.yml @@ -15,7 +15,7 @@ jobs: strategy: matrix: - node-version: [14.x, 16.x] + node-version: [18.x, 20.x] # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ steps: From 204409df544825041a586865cfb6bb124288e48f Mon Sep 17 00:00:00 2001 From: Andreas Richter <708186+richtera@users.noreply.github.com> Date: Fri, 5 Jan 2024 08:30:03 -0500 Subject: [PATCH 02/27] fix: Add workaround to read (bytes4,URI), repair none vs unknown signature. --- src/constants/constants.ts | 2 +- src/lib/encoder.test.ts | 20 +++++++++++++++++++- src/lib/encoder.ts | 37 +++++++++++++++++++++++++++++++------ 3 files changed, 51 insertions(+), 8 deletions(-) diff --git a/src/constants/constants.ts b/src/constants/constants.ts index 3673cc85..164a79b0 100644 --- a/src/constants/constants.ts +++ b/src/constants/constants.ts @@ -92,7 +92,7 @@ export const METHODS: Record = { }, }; -export const UNKNOWN_VERIFICATION_METHOD = 'unknown'; +export const NONE_VERIFICATION_METHOD = '0x00000000'; export enum SUPPORTED_VERIFICATION_METHOD_STRINGS { KECCAK256_UTF8 = 'keccak256(utf8)', diff --git a/src/lib/encoder.test.ts b/src/lib/encoder.test.ts index d2b5cd5b..3d5a8826 100644 --- a/src/lib/encoder.test.ts +++ b/src/lib/encoder.test.ts @@ -32,6 +32,7 @@ import { decodeValueContent, } from './encoder'; import { + NONE_VERIFICATION_METHOD, SUPPORTED_VERIFICATION_METHOD_HASHES, SUPPORTED_VERIFICATION_METHOD_STRINGS, } from '../constants/constants'; @@ -924,6 +925,20 @@ describe('encoder', () => { encodedValue: '0x00006f357c6a0020027547537d35728a741470df1ccf65de10b454ca0def7c5c20b257b7b8d16168687474703a2f2f746573742e636f6d2f61737365742e676c62', }, + { + valueContent: 'VerifiableURI', // Actual content is (bytes4,URI) + decodedValue: { + verification: { + method: NONE_VERIFICATION_METHOD, + data: '0x', + }, + url: 'https://name.universal.page/', + }, + encodedValue: + '0x6f357c6a68747470733a2f2f6e616d652e756e6976657273616c2e706167652f', + reencodedValue: + '0x000000000000000068747470733a2f2f6e616d652e756e6976657273616c2e706167652f', + }, { valueContent: 'BitArray', encodedValue: @@ -952,7 +967,10 @@ describe('encoder', () => { encodeValueContent(testCase.valueContent, testCase.decodedValue); - assert.deepStrictEqual(encodedValue, testCase.encodedValue); + assert.deepStrictEqual( + encodedValue, + testCase.reencodedValue || testCase.encodedValue, + ); assert.deepStrictEqual( encodedValue !== false ? decodeValueContent(testCase.valueContent, encodedValue) diff --git a/src/lib/encoder.ts b/src/lib/encoder.ts index 3838fda3..b2599ab4 100644 --- a/src/lib/encoder.ts +++ b/src/lib/encoder.ts @@ -49,7 +49,7 @@ import { AssetURLEncode } from '../types/encodeData'; import { SUPPORTED_VERIFICATION_METHOD_STRINGS, - UNKNOWN_VERIFICATION_METHOD, + NONE_VERIFICATION_METHOD, } from '../constants/constants'; import { getVerificationMethod, @@ -72,7 +72,7 @@ const encodeDataSourceWithHash = ( dataSource: string, ): string => { const verificationMethod = getVerificationMethod( - verification?.method || UNKNOWN_VERIFICATION_METHOD, + verification?.method || '0x00000000', ); return [ '0x0000', @@ -119,7 +119,11 @@ const decodeDataSourceWithHash = (value: string): URLDataWithHash => { return { verification: { - method: verificationMethod?.name || UNKNOWN_VERIFICATION_METHOD, + method: + verificationMethod?.name || + (verificationMethodSignature === '0x00000000' + ? NONE_VERIFICATION_METHOD + : verificationMethodSignature), data: dataHash, }, url: dataSource, @@ -129,12 +133,33 @@ const decodeDataSourceWithHash = (value: string): URLDataWithHash => { const verificationMethodSignature = value.slice(0, 10); const verificationMethod = getVerificationMethod(verificationMethodSignature); const encodedData = value.slice(10); // Rest of data string after function hash + + try { + const dataSource = hexToUtf8('0x' + encodedData); // Get as URI + if (encodedData.length < 64 || /^(https?|ipfs):\/\//.test(dataSource)) { + console.log(dataSource); + return { + verification: { + method: NONE_VERIFICATION_METHOD, + data: '0x', + }, + url: dataSource, + }; + } + } catch { + // ignore + } + const dataHash = '0x' + encodedData.slice(0, 64); // Get jsonHash 32 bytes const dataSource = hexToUtf8('0x' + encodedData.slice(64)); // Get remainder as URI return { verification: { - method: verificationMethod?.name || UNKNOWN_VERIFICATION_METHOD, + method: + verificationMethod?.name || + (verificationMethodSignature === '0x00000000' + ? NONE_VERIFICATION_METHOD + : verificationMethodSignature), data: dataHash, }, url: dataSource, @@ -707,7 +732,7 @@ export const valueContentEncodingMap = ( ); } - if (!hashedJson) { + if (!hashedJson && method !== NONE_VERIFICATION_METHOD) { throw new Error( 'You have to provide either the verification.data or the json via the respective properties', ); @@ -718,7 +743,7 @@ export const valueContentEncodingMap = ( method: (method as SUPPORTED_VERIFICATION_METHOD_STRINGS) || SUPPORTED_VERIFICATION_METHOD_STRINGS.KECCAK256_UTF8, - data: hashedJson, + data: hashedJson || '', }, url, ); From 5c8f2285d2d23c59ef9707a54e419b55d25e4136 Mon Sep 17 00:00:00 2001 From: Andreas Richter <708186+richtera@users.noreply.github.com> Date: Fri, 5 Jan 2024 11:59:18 -0500 Subject: [PATCH 03/27] fix: Repair console.log and expand types of URLs (ar://, ipfs://, https\?://, data:) --- src/lib/encoder.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/lib/encoder.ts b/src/lib/encoder.ts index b2599ab4..2791a5a0 100644 --- a/src/lib/encoder.ts +++ b/src/lib/encoder.ts @@ -136,8 +136,7 @@ const decodeDataSourceWithHash = (value: string): URLDataWithHash => { try { const dataSource = hexToUtf8('0x' + encodedData); // Get as URI - if (encodedData.length < 64 || /^(https?|ipfs):\/\//.test(dataSource)) { - console.log(dataSource); + if (encodedData.length < 64 || /^[a-z]{2,}:/.test(dataSource)) { return { verification: { method: NONE_VERIFICATION_METHOD, From a3dd604964c9c9221ff421fe668faa8fd363101a Mon Sep 17 00:00:00 2001 From: Andreas Richter <708186+richtera@users.noreply.github.com> Date: Fri, 5 Jan 2024 12:10:41 -0500 Subject: [PATCH 04/27] fix: Missing commit --- src/lib/encoder.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/encoder.ts b/src/lib/encoder.ts index 2791a5a0..5e064850 100644 --- a/src/lib/encoder.ts +++ b/src/lib/encoder.ts @@ -136,7 +136,7 @@ const decodeDataSourceWithHash = (value: string): URLDataWithHash => { try { const dataSource = hexToUtf8('0x' + encodedData); // Get as URI - if (encodedData.length < 64 || /^[a-z]{2,}:/.test(dataSource)) { + if (encodedData.length < 64 || /^[a-z]{2,}:[/\S]/.test(dataSource)) { return { verification: { method: NONE_VERIFICATION_METHOD, From cd6152a88ca7a1c98184799b6fdc6982ff8a1cd6 Mon Sep 17 00:00:00 2001 From: Andreas Richter <708186+richtera@users.noreply.github.com> Date: Fri, 5 Jan 2024 14:56:49 -0500 Subject: [PATCH 05/27] fix: Remove unnecessary special case for 0x00000000 --- src/lib/encoder.ts | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/lib/encoder.ts b/src/lib/encoder.ts index 5e064850..1b7e552c 100644 --- a/src/lib/encoder.ts +++ b/src/lib/encoder.ts @@ -119,11 +119,7 @@ const decodeDataSourceWithHash = (value: string): URLDataWithHash => { return { verification: { - method: - verificationMethod?.name || - (verificationMethodSignature === '0x00000000' - ? NONE_VERIFICATION_METHOD - : verificationMethodSignature), + method: verificationMethod?.name || verificationMethodSignature, data: dataHash, }, url: dataSource, @@ -154,11 +150,7 @@ const decodeDataSourceWithHash = (value: string): URLDataWithHash => { return { verification: { - method: - verificationMethod?.name || - (verificationMethodSignature === '0x00000000' - ? NONE_VERIFICATION_METHOD - : verificationMethodSignature), + method: verificationMethod?.name || verificationMethodSignature, data: dataHash, }, url: dataSource, From 3d6a5a65db5049b678d0d54149e9b11ea2479999 Mon Sep 17 00:00:00 2001 From: Andreas Richter <708186+richtera@users.noreply.github.com> Date: Fri, 5 Jan 2024 15:03:40 -0500 Subject: [PATCH 06/27] fix: Cleanup a bit with comments. --- src/lib/encoder.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/lib/encoder.ts b/src/lib/encoder.ts index 1b7e552c..5f6b520f 100644 --- a/src/lib/encoder.ts +++ b/src/lib/encoder.ts @@ -95,6 +95,9 @@ const encodeDataSourceWithHash = ( const decodeDataSourceWithHash = (value: string): URLDataWithHash => { if (value.slice(0, 6) === '0x0000') { + // DEAL with VerifiableURI + // NOTE: A JSONURL with a 0x00000000 verification method is invalid. + /* 0 1 2 3 4 5 6 7 8 12345678901234567890123456789012345678901234567890123456789012345678901234567890 @@ -126,13 +129,23 @@ const decodeDataSourceWithHash = (value: string): URLDataWithHash => { }; } + // @Deprecated code here: + + // Eventually we should no longer have JSONURL, AssetURL or (bytes4,URI) + + // DEAL with JSONURL + const verificationMethodSignature = value.slice(0, 10); const verificationMethod = getVerificationMethod(verificationMethodSignature); const encodedData = value.slice(10); // Rest of data string after function hash try { + // Special case where JSONURL is really (bytes4,URI) as specified + // by the old version of LSP8TokenMetadataBaseURI + // Catch error in case the buffor is not convertable to utf8. const dataSource = hexToUtf8('0x' + encodedData); // Get as URI if (encodedData.length < 64 || /^[a-z]{2,}:[/\S]/.test(dataSource)) { + // If the verification data starts with a utf8 sequence that looks like https:/ or data: or ar:/ for example. return { verification: { method: NONE_VERIFICATION_METHOD, From 44834b8ed517e869ad99920b2853e30213a8d7b5 Mon Sep 17 00:00:00 2001 From: Andreas Richter <708186+richtera@users.noreply.github.com> Date: Mon, 15 Jan 2024 11:30:56 -0500 Subject: [PATCH 07/27] fix: Repair problems with IPFS, fetch and VerifiableURI --- src/lib/encoder.ts | 7 ++++--- src/lib/getDataFromExternalSources.ts | 4 +++- src/lib/provider-wrapper-utils.ts | 6 +++--- src/provider/providerWrapper.ts | 8 ++++---- src/types/JsonRpc.ts | 2 +- 5 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/lib/encoder.ts b/src/lib/encoder.ts index 5f6b520f..0c51d712 100644 --- a/src/lib/encoder.ts +++ b/src/lib/encoder.ts @@ -529,11 +529,12 @@ const valueTypeEncodingMap = ( ); } + // Although this is "wrong" making this a real Exception prevents people from using the library + // rather than encouraging them to fix the problem. Making this a console error. const numberOfBytes = countNumberOfBytes(value); - if (numberOfBytes > (uintLength as number) / 8) { - throw new Error( - `Can't convert hex value ${value} to ${type}. Too many bytes. ${numberOfBytes} > 16`, + console.error( + `Invalid length hex value ${value} to ${type}. Too many bytes. ${numberOfBytes} > 16`, ); } diff --git a/src/lib/getDataFromExternalSources.ts b/src/lib/getDataFromExternalSources.ts index dd0a96ec..c1b1a0ba 100644 --- a/src/lib/getDataFromExternalSources.ts +++ b/src/lib/getDataFromExternalSources.ts @@ -77,6 +77,9 @@ export const getDataFromExternalSources = ( const { url } = patchIPFSUrlsIfApplicable(urlDataWithHash, ipfsGateway); receivedData = await fetch(url).then(async (response) => { + if (!response.ok) { + return undefined; + } if ( urlDataWithHash.verification?.method === SUPPORTED_VERIFICATION_METHOD_STRINGS.KECCAK256_BYTES @@ -85,7 +88,6 @@ export const getDataFromExternalSources = ( .arrayBuffer() .then((buffer) => new Uint8Array(buffer)); } - return response.json(); }); } catch (error) { diff --git a/src/lib/provider-wrapper-utils.ts b/src/lib/provider-wrapper-utils.ts index 0a0d52a0..9b56cce3 100644 --- a/src/lib/provider-wrapper-utils.ts +++ b/src/lib/provider-wrapper-utils.ts @@ -50,7 +50,7 @@ export function decodeResult(method: Method, hexString: string) { const constructJSONRPCParams = ( address: string, method: Method, - gasInfo: number, + gasInfo?: number, methodParam?: string, ): JsonRpcEthereumProviderParamsWithLatest => { const data = methodParam @@ -61,7 +61,7 @@ const constructJSONRPCParams = ( { to: address, value: METHODS[method].value, - gas: numberToHex(gasInfo), + ...(gasInfo ? { gas: numberToHex(gasInfo) } : {}), data, }, 'latest', @@ -71,7 +71,7 @@ const constructJSONRPCParams = ( export function constructJSONRPC( address: string, method: Method, - gasInfo: number, + gasInfo?: number, methodParam?: string, ): JsonRpc { idCount += 1; diff --git a/src/provider/providerWrapper.ts b/src/provider/providerWrapper.ts index a3f235d1..9ec748e4 100644 --- a/src/provider/providerWrapper.ts +++ b/src/provider/providerWrapper.ts @@ -158,7 +158,7 @@ export class ProviderWrapper { constructJSONRPC( address, Method.IS_VALID_SIGNATURE, - this.gas, + undefined, // this.gas, encodedParams, ), ); @@ -183,7 +183,7 @@ export class ProviderWrapper { constructJSONRPC( address, Method.IS_VALID_SIGNATURE, - this.gas, + undefined, // this.gas, encodedParams, ), ]); @@ -245,7 +245,7 @@ export class ProviderWrapper { constructJSONRPC( address, method, - this.gas, + undefined, // this.gas, abiCoder.encodeParameter('bytes32[]', keyHashes), ), ); @@ -262,7 +262,7 @@ export class ProviderWrapper { constructJSONRPC( address, method, - this.gas, + undefined, // this.gas, abiCoder.encodeParameter('bytes32[]', keyHashes), ), ]; diff --git a/src/types/JsonRpc.ts b/src/types/JsonRpc.ts index 13d9a801..96f9c6af 100644 --- a/src/types/JsonRpc.ts +++ b/src/types/JsonRpc.ts @@ -1,6 +1,6 @@ interface JsonRpcEthereumProviderParams { to: string; - gas: string; + gas?: string; value: string; data; } From 25756d09cecfec5d13d6e4fb6ed842c4e3d77734 Mon Sep 17 00:00:00 2001 From: Andreas Richter <708186+richtera@users.noreply.github.com> Date: Mon, 15 Jan 2024 14:17:33 -0500 Subject: [PATCH 08/27] fix: Repair to not throw errors when data is not authentica or not accessible withing getDataFromExternalSources. --- src/lib/getDataFromExternalSources.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/lib/getDataFromExternalSources.ts b/src/lib/getDataFromExternalSources.ts index c1b1a0ba..927bf81a 100644 --- a/src/lib/getDataFromExternalSources.ts +++ b/src/lib/getDataFromExternalSources.ts @@ -90,14 +90,17 @@ export const getDataFromExternalSources = ( } return response.json(); }); + if (isDataAuthentic(receivedData, urlDataWithHash.verification)) { + return dataEntry; + } + console.error( + `GET request to ${urlDataWithHash.url} did not correctly validate`, + ); } catch (error) { console.error(error, `GET request to ${urlDataWithHash.url} failed`); - throw error; } - - return isDataAuthentic(receivedData, urlDataWithHash.verification) - ? { ...dataEntry, value: receivedData } - : { ...dataEntry, value: null }; + // Invalid data + return { ...dataEntry, value: null }; }); return Promise.all(promises); From 224eb9ecb1b16f373f2ad17ee9d3efab08dda53b Mon Sep 17 00:00:00 2001 From: Andreas Richter <708186+richtera@users.noreply.github.com> Date: Mon, 15 Jan 2024 15:25:25 -0500 Subject: [PATCH 09/27] fix: Repair and enhance test scripts --- src/index.test.ts | 19 +++++++-- src/lib/decodeData.test.ts | 57 +++++++++++++++++++++++++++ src/lib/encoder.test.ts | 18 +++++---- src/lib/encoder.ts | 15 ++++++- src/lib/getDataFromExternalSources.ts | 9 ++++- 5 files changed, 104 insertions(+), 14 deletions(-) diff --git a/src/index.test.ts b/src/index.test.ts index 6e444748..76b9c63b 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -561,8 +561,15 @@ describe('Running @erc725/erc725.js tests...', () => { const jsonString = `{"LSP3Profile":{"profileImage":"ipfs://QmYo8yg4zzmdu26NSvtsoKeU5oVR6h2ohmoa2Cx5i91mPf","backgroundImage":"ipfs://QmZF5pxDJcB8eVvCd74rsXBFXhWL3S1XR5tty2cy1a58Ew","description":"Beautiful clothing that doesn't cost the Earth. A sustainable designer based in London Patrick works with brand partners to refocus on systemic change centred around creative education. "}}`; const fetchStub = sinon.stub(global, 'fetch'); - fetchStub.onCall(0).returns(Promise.resolve(new Response(jsonString))); + fetchStub.onCall(0).returns( + Promise.resolve( + new Response(jsonString, { + headers: { 'content-type': 'application/json' }, + }), + ), + ); const result = await erc725.fetchData('TestJSONURL'); + console.log(fetchStub.getCalls(), result); fetchStub.restore(); assert.deepStrictEqual(result, { @@ -584,7 +591,7 @@ describe('Running @erc725/erc725.js tests...', () => { [contractVersion.interface], ); - const ipfsGateway = 'https://2eff.lukso.dev'; + const ipfsGateway = 'https://api.universalprofile.cloud'; const erc725 = new ERC725([testJSONURLSchema], address, provider, { ipfsGateway, @@ -593,7 +600,13 @@ describe('Running @erc725/erc725.js tests...', () => { const jsonString = `{"LSP3Profile":{"profileImage":"ipfs://QmYo8yg4zzmdu26NSvtsoKeU5oVR6h2ohmoa2Cx5i91mPf","backgroundImage":"ipfs://QmZF5pxDJcB8eVvCd74rsXBFXhWL3S1XR5tty2cy1a58Ew","description":"Beautiful clothing that doesn't cost the Earth. A sustainable designer based in London Patrick works with brand partners to refocus on systemic change centred around creative education. "}}`; const fetchStub = sinon.stub(global, 'fetch'); - fetchStub.onCall(0).returns(Promise.resolve(new Response(jsonString))); + fetchStub.onCall(0).returns( + Promise.resolve( + new Response(jsonString, { + headers: { 'content-type': 'application/json' }, + }), + ), + ); const result = await erc725.fetchData('TestJSONURL'); assert.deepStrictEqual(result, { key: testJSONURLSchema.key, diff --git a/src/lib/decodeData.test.ts b/src/lib/decodeData.test.ts index 22ea7ea2..65845c33 100644 --- a/src/lib/decodeData.test.ts +++ b/src/lib/decodeData.test.ts @@ -91,6 +91,11 @@ describe('decodeData', () => { value: '0x6f357c6a820464ddfac1bec070cc14a8daf04129871d458f2ca94368aae8391311af6361696670733a2f2f516d597231564a4c776572673670456f73636468564775676f3339706136727963455a4c6a7452504466573834554178', }, + { + keyName: 'JSONURLCase', + value: + '0x8019f9b1820464ddfac1bec070cc14a8daf04129871d458f2ca94368aae8391311af6361696670733a2f2f516d597231564a4c776572673670456f73636468564775676f3339706136727963455a4c6a7452504466573834554178', + }, { keyName: 'AssetURLCase', value: @@ -105,6 +110,13 @@ describe('decodeData', () => { valueType: 'bytes', valueContent: 'JSONURL', // Deprecated - We keep it for backward compatibility between v0.21.3 and v0.22.0 }, + { + name: 'JSONURLCase2', + key: '0x9136feeb09af67b63993b586ce46a43bd3456990d3fdb39d07beab9dee8d5910', + keyType: 'Singleton', + valueType: 'bytes', + valueContent: 'JSONURL', // Deprecated - We keep it for backward compatibility between v0.21.3 and v0.22.0 + }, { name: 'AssetURLCase', key: '0xbda5878fa57d8da097bf7cfd78c28e75f2c2c7b028e4e056d16d7e4b83f98081', @@ -123,6 +135,13 @@ describe('decodeData', () => { }, url: 'ifps://QmYr1VJLwerg6pEoscdhVGugo39pa6rycEZLjtRPDfW84UAx', }, + { + verification: { + method: 'keccak256(bytes)', // 0x8019f9b1 + data: '0x820464ddfac1bec070cc14a8daf04129871d458f2ca94368aae8391311af6361', + }, + url: 'ifps://QmYr1VJLwerg6pEoscdhVGugo39pa6rycEZLjtRPDfW84UAx', + }, { verification: { method: 'keccak256(bytes)', // 0x8019f9b1 @@ -206,6 +225,44 @@ describe('decodeData', () => { ]); }); + it('parses type Array correctly (even with uint256)', () => { + const decodedData = decodeData( + { + keyName: 'LSP12IssuedAssets[]', + value: [ + { + key: '0x7c8c3416d6cda87cd42c71ea1843df28ac4850354f988d55ee2eaa47b6dc05cd', + value: + '0x0000000000000000000000000000000000000000000000000000000000000002', + }, + { + key: '0x7c8c3416d6cda87cd42c71ea1843df2800000000000000000000000000000000', + value: '0xd94353d9b005b3c0a9da169b768a31c57844e490', + }, + { + key: '0x7c8c3416d6cda87cd42c71ea1843df2800000000000000000000000000000001', + value: '0xdaea594e385fc724449e3118b2db7e86dfba1826', + }, + ], + }, + [ + { + name: 'LSP12IssuedAssets[]', + key: '0x7c8c3416d6cda87cd42c71ea1843df28ac4850354f988d55ee2eaa47b6dc05cd', + keyType: 'Array', + valueContent: 'Address', + valueType: 'address', + }, + ], + ); + + expect(decodedData.name).to.eql('LSP12IssuedAssets[]'); + expect(decodedData.value).to.eql([ + '0xD94353D9B005B3c0A9Da169b768a31C57844e490', + '0xDaea594E385Fc724449E3118B2Db7E86dFBa1826', + ]); + }); + it('decodes dynamic keys', () => { const decodedData = decodeData( [ diff --git a/src/lib/encoder.test.ts b/src/lib/encoder.test.ts index 3d5a8826..3cc5d68a 100644 --- a/src/lib/encoder.test.ts +++ b/src/lib/encoder.test.ts @@ -38,7 +38,7 @@ import { } from '../constants/constants'; import { URLDataToEncode, URLDataWithHash } from '../types'; -describe('encoder', () => { +describe.only('encoder', () => { describe('valueType', () => { describe('`bool`/`boolean` type', () => { const validTestCases = [ @@ -667,13 +667,15 @@ describe('encoder', () => { ); }); - it('throws when trying to decode a bytes17 as `uint128`', () => { - expect(() => - decodeValueType('uint128', '0x000000000000000000000000000000ffff'), - ).to.throw( - "Can't convert hex value 0x000000000000000000000000000000ffff to uint128. Too many bytes. 17 > 16", - ); - }); + // We do not want this during decode. During encode we enforce the correct sizes. + // + // it('throws when trying to decode a bytes17 as `uint128`', () => { + // expect(() => + // decodeValueType('uint128', '0x000000000000000000000000000000ffff'), + // ).to.throw( + // "Can't convert hex value 0x000000000000000000000000000000ffff to uint128. Too many bytes. 17 > 16", + // ); + // }); }); describe('`type[CompactBytesArray]` (of static types)', () => { diff --git a/src/lib/encoder.ts b/src/lib/encoder.ts index 0c51d712..a87068a6 100644 --- a/src/lib/encoder.ts +++ b/src/lib/encoder.ts @@ -506,14 +506,25 @@ const valueTypeEncodingMap = ( ); } const abiEncodedValue = abiCoder.encodeParameter(type, value); - + console.log(value, abiEncodedValue); const bytesArray = hexToBytes(abiEncodedValue); const numberOfBytes = (uintLength as number) / 8; - // abi-encoding always pad to 32 bytes. We need to keep the `n` rightmost bytes. // where `n` = `numberOfBytes` const startIndex = 32 - numberOfBytes; + console.log( + bytesArray, + startIndex, + bytesArray.slice(0, startIndex), + bytesArray.slice(startIndex), + ); + if (bytesArray.slice(0, startIndex).some((byte) => byte !== 0)) { + throw Error( + `Number ${value} is too large to be represented as ${type}`, + ); + } + return bytesToHex(bytesArray.slice(startIndex)); }, decode: (value: string) => { diff --git a/src/lib/getDataFromExternalSources.ts b/src/lib/getDataFromExternalSources.ts index 927bf81a..d0cb9349 100644 --- a/src/lib/getDataFromExternalSources.ts +++ b/src/lib/getDataFromExternalSources.ts @@ -77,6 +77,7 @@ export const getDataFromExternalSources = ( const { url } = patchIPFSUrlsIfApplicable(urlDataWithHash, ipfsGateway); receivedData = await fetch(url).then(async (response) => { + console.log(receivedData, urlDataWithHash, response); if (!response.ok) { return undefined; } @@ -90,8 +91,14 @@ export const getDataFromExternalSources = ( } return response.json(); }); + console.log( + receivedData, + urlDataWithHash, + receivedData, + urlDataWithHash.verification, + ); if (isDataAuthentic(receivedData, urlDataWithHash.verification)) { - return dataEntry; + return { ...dataEntry, value: receivedData }; } console.error( `GET request to ${urlDataWithHash.url} did not correctly validate`, From 620b60632e50ca59abedae9935f8b75d188278e0 Mon Sep 17 00:00:00 2001 From: Andreas Richter <708186+richtera@users.noreply.github.com> Date: Mon, 15 Jan 2024 16:09:36 -0500 Subject: [PATCH 10/27] fix: More fixes --- src/lib/encoder.ts | 10 +--------- src/lib/getDataFromExternalSources.ts | 7 ------- 2 files changed, 1 insertion(+), 16 deletions(-) diff --git a/src/lib/encoder.ts b/src/lib/encoder.ts index a87068a6..dc18e95f 100644 --- a/src/lib/encoder.ts +++ b/src/lib/encoder.ts @@ -506,19 +506,11 @@ const valueTypeEncodingMap = ( ); } const abiEncodedValue = abiCoder.encodeParameter(type, value); - console.log(value, abiEncodedValue); const bytesArray = hexToBytes(abiEncodedValue); const numberOfBytes = (uintLength as number) / 8; // abi-encoding always pad to 32 bytes. We need to keep the `n` rightmost bytes. // where `n` = `numberOfBytes` const startIndex = 32 - numberOfBytes; - - console.log( - bytesArray, - startIndex, - bytesArray.slice(0, startIndex), - bytesArray.slice(startIndex), - ); if (bytesArray.slice(0, startIndex).some((byte) => byte !== 0)) { throw Error( `Number ${value} is too large to be represented as ${type}`, @@ -794,7 +786,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; } diff --git a/src/lib/getDataFromExternalSources.ts b/src/lib/getDataFromExternalSources.ts index d0cb9349..bf91a554 100644 --- a/src/lib/getDataFromExternalSources.ts +++ b/src/lib/getDataFromExternalSources.ts @@ -77,7 +77,6 @@ export const getDataFromExternalSources = ( const { url } = patchIPFSUrlsIfApplicable(urlDataWithHash, ipfsGateway); receivedData = await fetch(url).then(async (response) => { - console.log(receivedData, urlDataWithHash, response); if (!response.ok) { return undefined; } @@ -91,12 +90,6 @@ export const getDataFromExternalSources = ( } return response.json(); }); - console.log( - receivedData, - urlDataWithHash, - receivedData, - urlDataWithHash.verification, - ); if (isDataAuthentic(receivedData, urlDataWithHash.verification)) { return { ...dataEntry, value: receivedData }; } From d5ef3c9965e2c5ad343f88a9c30a6281e9fa169b Mon Sep 17 00:00:00 2001 From: Andreas Richter <708186+richtera@users.noreply.github.com> Date: Mon, 15 Jan 2024 17:16:32 -0500 Subject: [PATCH 11/27] fix: Remove .only call for testing. --- src/lib/encoder.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/encoder.test.ts b/src/lib/encoder.test.ts index 3cc5d68a..e670bbbd 100644 --- a/src/lib/encoder.test.ts +++ b/src/lib/encoder.test.ts @@ -38,7 +38,7 @@ import { } from '../constants/constants'; import { URLDataToEncode, URLDataWithHash } from '../types'; -describe.only('encoder', () => { +describe('encoder', () => { describe('valueType', () => { describe('`bool`/`boolean` type', () => { const validTestCases = [ From 0a5245230455324e9911e4e14992c50e6f645ba2 Mon Sep 17 00:00:00 2001 From: Andreas Richter <708186+richtera@users.noreply.github.com> Date: Tue, 16 Jan 2024 07:08:52 -0500 Subject: [PATCH 12/27] fix: Repair as per PR review --- src/index.test.ts | 1 - src/lib/encoder.test.ts | 21 ++++++++++++--------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/index.test.ts b/src/index.test.ts index 76b9c63b..b09c8889 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -569,7 +569,6 @@ describe('Running @erc725/erc725.js tests...', () => { ), ); const result = await erc725.fetchData('TestJSONURL'); - console.log(fetchStub.getCalls(), result); fetchStub.restore(); assert.deepStrictEqual(result, { diff --git a/src/lib/encoder.test.ts b/src/lib/encoder.test.ts index e670bbbd..07e40637 100644 --- a/src/lib/encoder.test.ts +++ b/src/lib/encoder.test.ts @@ -667,15 +667,18 @@ describe('encoder', () => { ); }); - // We do not want this during decode. During encode we enforce the correct sizes. - // - // it('throws when trying to decode a bytes17 as `uint128`', () => { - // expect(() => - // decodeValueType('uint128', '0x000000000000000000000000000000ffff'), - // ).to.throw( - // "Can't convert hex value 0x000000000000000000000000000000ffff to uint128. Too many bytes. 17 > 16", - // ); - // }); + // We do not want to throw an Exception during decode. During encode we enforce the correct sizes. + // If there is a value size exception then we should throw. + + it('throws when trying to decode a bytes17 as `uint128`', () => { + assert.equal( + decodeValueType('uint128', '0x000000000000000000000000000000ffff'), + '0xffff', + ); + assert.throws(() => + decodeValueType('uint128', '0x0100000000000000000000000000000000'), + ); + }); }); describe('`type[CompactBytesArray]` (of static types)', () => { From 2a18f2bf1a24547e71cad6953f5144761aa2cc9a Mon Sep 17 00:00:00 2001 From: Andreas Richter <708186+richtera@users.noreply.github.com> Date: Tue, 16 Jan 2024 07:10:32 -0500 Subject: [PATCH 13/27] fix: Additional PR repair --- src/lib/encoder.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/encoder.ts b/src/lib/encoder.ts index dc18e95f..972879a2 100644 --- a/src/lib/encoder.ts +++ b/src/lib/encoder.ts @@ -72,7 +72,7 @@ const encodeDataSourceWithHash = ( dataSource: string, ): string => { const verificationMethod = getVerificationMethod( - verification?.method || '0x00000000', + verification?.method || NONE_VERIFICATION_METHOD, ); return [ '0x0000', From e7fd19bc27aa89fae410b9633ef5ccdf13f33f0e Mon Sep 17 00:00:00 2001 From: CJ42 Date: Wed, 10 Jan 2024 09:25:41 +0000 Subject: [PATCH 14/27] feat: add permission related to 4337 --- docs/classes/ERC725.md | 3 ++- src/constants/constants.ts | 1 + src/index.test.ts | 8 ++++++++ src/index.ts | 1 + src/types/Method.ts | 1 + 5 files changed, 13 insertions(+), 1 deletion(-) diff --git a/docs/classes/ERC725.md b/docs/classes/ERC725.md index a5ecd2ae..d26b5c78 100644 --- a/docs/classes/ERC725.md +++ b/docs/classes/ERC725.md @@ -754,7 +754,8 @@ ERC725.encodePermissions({ ENCRYPT: false, DECRYPT: false, SIGN: false, - EXECUTE_RELAY_CALL: false + EXECUTE_RELAY_CALL: false, + ERC4337_PERMISSION: false }), // '0x0000000000000000000000000000000000000000000000000000000000000110' diff --git a/src/constants/constants.ts b/src/constants/constants.ts index 3673cc85..30429317 100644 --- a/src/constants/constants.ts +++ b/src/constants/constants.ts @@ -167,6 +167,7 @@ export const LSP6_DEFAULT_PERMISSIONS = { DECRYPT : "0x0000000000000000000000000000000000000000000000000000000000100000", SIGN : "0x0000000000000000000000000000000000000000000000000000000000200000", EXECUTE_RELAY_CALL : "0x0000000000000000000000000000000000000000000000000000000000400000", + ERC4337_PERMISSION : "0x0000000000000000000000000000000000000000000000000000000000800000" }; export const LSP6_ALL_PERMISSIONS = diff --git a/src/index.test.ts b/src/index.test.ts index 6e444748..576cb834 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -1067,6 +1067,7 @@ describe('Running @erc725/erc725.js tests...', () => { DECRYPT: true, SIGN: true, EXECUTE_RELAY_CALL: false, + ERC4337_PERMISSION: false, }, hex: '0x00000000000000000000000000000000000000000000000000000000003f3f7f', }, @@ -1095,6 +1096,7 @@ describe('Running @erc725/erc725.js tests...', () => { DECRYPT: false, SIGN: false, EXECUTE_RELAY_CALL: false, + ERC4337_PERMISSION: false, }, hex: '0x0000000000000000000000000000000000000000000000000000000000000000', }, @@ -1123,6 +1125,7 @@ describe('Running @erc725/erc725.js tests...', () => { DECRYPT: false, SIGN: true, EXECUTE_RELAY_CALL: false, + ERC4337_PERMISSION: false, }, hex: '0x0000000000000000000000000000000000000000000000000000000000200a00', }, @@ -1151,6 +1154,7 @@ describe('Running @erc725/erc725.js tests...', () => { DECRYPT: false, SIGN: false, EXECUTE_RELAY_CALL: false, + ERC4337_PERMISSION: false, }, hex: '0x0000000000000000000000000000000000000000000000000000000000040800', }, @@ -1179,6 +1183,7 @@ describe('Running @erc725/erc725.js tests...', () => { DECRYPT: false, SIGN: false, EXECUTE_RELAY_CALL: false, + ERC4337_PERMISSION: false, }, hex: '0x0000000000000000000000000000000000000000000000000000000000040004', }, @@ -1207,6 +1212,7 @@ describe('Running @erc725/erc725.js tests...', () => { DECRYPT: false, SIGN: false, EXECUTE_RELAY_CALL: false, + ERC4337_PERMISSION: false, }, hex: '0x0000000000000000000000000000000000000000000000000000000000000a00', }, @@ -1288,6 +1294,7 @@ describe('Running @erc725/erc725.js tests...', () => { DECRYPT: true, SIGN: true, EXECUTE_RELAY_CALL: true, + ERC4337_PERMISSION: true, }, ); assert.deepStrictEqual( @@ -1318,6 +1325,7 @@ describe('Running @erc725/erc725.js tests...', () => { DECRYPT: true, SIGN: true, EXECUTE_RELAY_CALL: true, + ERC4337_PERMISSION: true, }, ); }); diff --git a/src/index.ts b/src/index.ts index e7924b75..d75df5ae 100644 --- a/src/index.ts +++ b/src/index.ts @@ -500,6 +500,7 @@ export class ERC725 { DECRYPT: false, SIGN: false, EXECUTE_RELAY_CALL: false, + ERC4337_PERMISSION: false, }; const permissionsToTest = Object.keys(LSP6_DEFAULT_PERMISSIONS); diff --git a/src/types/Method.ts b/src/types/Method.ts index 1c794a67..681aee85 100644 --- a/src/types/Method.ts +++ b/src/types/Method.ts @@ -47,4 +47,5 @@ export interface Permissions { DECRYPT?: boolean; SIGN?: boolean; EXECUTE_RELAY_CALL?: boolean; + ERC4337_PERMISSION?: boolean; } From cdf4583c159d8c8e52a832bfe5af6b650d8945fa Mon Sep 17 00:00:00 2001 From: Andreas Richter <708186+richtera@users.noreply.github.com> Date: Tue, 16 Jan 2024 10:45:59 -0500 Subject: [PATCH 15/27] fix: Cleanup and handle situation where one of many keys fails. Return null. --- src/lib/decodeData.ts | 38 +++++++++++++++++--------------------- src/lib/encoder.ts | 35 ++++++++++++++++++++++------------- src/lib/getData.ts | 40 ++++++++++++++++++++++------------------ src/lib/utils.ts | 11 +++++++++++ 4 files changed, 72 insertions(+), 52 deletions(-) diff --git a/src/lib/decodeData.ts b/src/lib/decodeData.ts index 9cbe885e..7d676efa 100644 --- a/src/lib/decodeData.ts +++ b/src/lib/decodeData.ts @@ -312,37 +312,33 @@ export function decodeData( data: DecodeDataInput | DecodeDataInput[], schema: ERC725JSONSchema[], ): DecodeDataOutput | DecodeDataOutput[] { - const processDataInput = ({ - keyName, - dynamicKeyParts, - value, - }: DecodeDataInput) => { + const processDataInput = ( + { keyName, dynamicKeyParts, value }: DecodeDataInput, + throwException = true, + ) => { const isDynamic = isDynamicKeyName(keyName); - let schemaElement: ERC725JSONSchema; - if (isDynamic) { - schemaElement = getSchemaElement(schema, keyName, dynamicKeyParts); - - // NOTE: it might be confusing to use as the output will contain other keys as the ones used - // for the input - return { - key: schemaElement.key, - name: schemaElement.name, - value: decodeKey(schemaElement, value), - }; + const schemaElement: ERC725JSONSchema = isDynamic + ? getSchemaElement(schema, keyName, dynamicKeyParts) + : getSchemaElement(schema, keyName); + let decodedValue: any = null; + try { + decodedValue = decodeKey(schemaElement, value); + } catch (error) { + if (throwException) { + throw error; + } + console.error(error); } - - schemaElement = getSchemaElement(schema, keyName); - return { key: schemaElement.key, name: schemaElement.name, - value: decodeKey(schemaElement, value), + value: decodedValue, }; }; if (Array.isArray(data)) { - return data.map((dataInput) => processDataInput(dataInput)); + return data.map((dataInput) => processDataInput(dataInput, false)); } return processDataInput(data); diff --git a/src/lib/encoder.ts b/src/lib/encoder.ts index 972879a2..02922ab0 100644 --- a/src/lib/encoder.ts +++ b/src/lib/encoder.ts @@ -56,6 +56,7 @@ import { hashData, countNumberOfBytes, isValidUintSize, + countSignificantBits, } from './utils'; import { ERC725JSONSchemaValueType } from '../types/ERC725JSONSchema'; @@ -506,17 +507,20 @@ const valueTypeEncodingMap = ( ); } const abiEncodedValue = abiCoder.encodeParameter(type, value); + + const numberOfBits = countSignificantBits(abiEncodedValue); + if (numberOfBits > (uintLength as number)) { + throw new Error( + `Can't represent value ${value} as ${type}. To many bits required ${numberOfBits} > ${uintLength}`, + ); + } + const bytesArray = hexToBytes(abiEncodedValue); const numberOfBytes = (uintLength as number) / 8; + // abi-encoding always pad to 32 bytes. We need to keep the `n` rightmost bytes. // where `n` = `numberOfBytes` const startIndex = 32 - numberOfBytes; - if (bytesArray.slice(0, startIndex).some((byte) => byte !== 0)) { - throw Error( - `Number ${value} is too large to be represented as ${type}`, - ); - } - return bytesToHex(bytesArray.slice(startIndex)); }, decode: (value: string) => { @@ -532,12 +536,17 @@ const valueTypeEncodingMap = ( ); } - // Although this is "wrong" making this a real Exception prevents people from using the library - // rather than encouraging them to fix the problem. Making this a console error. + const numberOfBits = countSignificantBits(value); + if (numberOfBits > (uintLength as number)) { + throw new Error( + `Can't represent value ${value} as ${type}. To many bits required ${numberOfBits} > ${uintLength}`, + ); + } + const numberOfBytes = countNumberOfBytes(value); if (numberOfBytes > (uintLength as number) / 8) { - console.error( - `Invalid length hex value ${value} to ${type}. Too many bytes. ${numberOfBytes} > 16`, + console.debug( + `Value ${value} for ${type} is too long but value contains only ${numberOfBits}. Too many bytes. ${numberOfBytes} > 16`, ); } @@ -740,7 +749,7 @@ export const valueContentEncodingMap = ( ); } - if (!hashedJson && method !== NONE_VERIFICATION_METHOD) { + if (!hashedJson) { throw new Error( 'You have to provide either the verification.data or the json via the respective properties', ); @@ -751,7 +760,7 @@ export const valueContentEncodingMap = ( method: (method as SUPPORTED_VERIFICATION_METHOD_STRINGS) || SUPPORTED_VERIFICATION_METHOD_STRINGS.KECCAK256_UTF8, - data: hashedJson || '', + data: hashedJson, }, url, ); @@ -786,7 +795,7 @@ export const valueContentEncodingMap = ( }, decode: (value: string) => { if (typeof value !== 'string' || !isHex(value)) { - console.error(`Value: ${value} is not hex.`); + console.log(`Value: ${value} is not hex.`); return null; } diff --git a/src/lib/getData.ts b/src/lib/getData.ts index 5d7cbc1e..acff7746 100644 --- a/src/lib/getData.ts +++ b/src/lib/getData.ts @@ -106,24 +106,28 @@ const getDataMultiple = async ( // Looks like it gets array even if not requested as it gets the arrays from the this.options.schemas? // eslint-disable-next-line no-restricted-syntax for (const keySchema of arraySchemas) { - const dataKeyValue = { - [keySchema.key]: { - key: keySchema.key, - value: keyValueMap[keySchema.key], - }, - }; - const arrayValues = await getArrayValues( - erc725Options, - keySchema, - dataKeyValue, - ); - - if (arrayValues && arrayValues.length > 0) { - arrayValues.push(dataKeyValue[keySchema.key]); // add the raw data array length - - schemasWithValue[ - schemasWithValue.findIndex((schema) => schema.key === keySchema.key) - ] = { ...keySchema, value: arrayValues }; + try { + const dataKeyValue = { + [keySchema.key]: { + key: keySchema.key, + value: keyValueMap[keySchema.key], + }, + }; + const arrayValues = await getArrayValues( + erc725Options, + keySchema, + dataKeyValue, + ); + + if (arrayValues && arrayValues.length > 0) { + arrayValues.push(dataKeyValue[keySchema.key]); // add the raw data array length + + schemasWithValue[ + schemasWithValue.findIndex((schema) => schema.key === keySchema.key) + ] = { ...keySchema, value: arrayValues }; + } + } catch (error) { + console.error(error); } } // ------- END ARRAY HANDLER ------- diff --git a/src/lib/utils.ts b/src/lib/utils.ts index d3230b13..2a986237 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -21,6 +21,7 @@ import { checkAddressChecksum, + hexToBytes, isAddress, leftPad, numberToHex, @@ -588,6 +589,16 @@ export function countNumberOfBytes(data: string) { return stripHexPrefix(data).length / 2; } +export function countSignificantBits(data = '0x0') { + const bytes = hexToBytes(data); + for (let i = 0; i < bytes.length; i++) { + if (bytes[i] !== 0) { + return (bytes.length - i - 1) * 8 + 32 - Math.clz32(bytes[i]); + } + } + return 0; +} + /** * Given an input string which can define dynamic types, will return an array with all types * In: From 038d97584075c89f93f51227666074cf11973a29 Mon Sep 17 00:00:00 2001 From: Andreas Richter <708186+richtera@users.noreply.github.com> Date: Tue, 16 Jan 2024 10:48:41 -0500 Subject: [PATCH 16/27] fix: Cleanup --- src/lib/encoder.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/encoder.ts b/src/lib/encoder.ts index 02922ab0..9594329a 100644 --- a/src/lib/encoder.ts +++ b/src/lib/encoder.ts @@ -760,7 +760,7 @@ export const valueContentEncodingMap = ( method: (method as SUPPORTED_VERIFICATION_METHOD_STRINGS) || SUPPORTED_VERIFICATION_METHOD_STRINGS.KECCAK256_UTF8, - data: hashedJson, + data: hashedJson || '0x', }, url, ); From fde2e0f41df3ecee90a4dfd8248dd9c2a7a72965 Mon Sep 17 00:00:00 2001 From: Andreas Richter <708186+richtera@users.noreply.github.com> Date: Tue, 16 Jan 2024 11:16:51 -0500 Subject: [PATCH 17/27] fix: Cleanup error handler inside of getDataFromExternalSources --- src/lib/getDataFromExternalSources.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/lib/getDataFromExternalSources.ts b/src/lib/getDataFromExternalSources.ts index bf91a554..1716bf11 100644 --- a/src/lib/getDataFromExternalSources.ts +++ b/src/lib/getDataFromExternalSources.ts @@ -56,29 +56,28 @@ export const getDataFromExternalSources = ( 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; + return { ...dataEntry, value: null }; } if (!dataEntry.value) { - return dataEntry; + return { ...dataEntry, value: null }; } 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; + return { ...dataEntry, value: null }; } const urlDataWithHash = dataEntry.value; // Type URLDataWithHash let receivedData; + const { url } = patchIPFSUrlsIfApplicable(urlDataWithHash, ipfsGateway); try { - const { url } = patchIPFSUrlsIfApplicable(urlDataWithHash, ipfsGateway); - receivedData = await fetch(url).then(async (response) => { if (!response.ok) { - return undefined; + throw new Error(response.statusText); } if ( urlDataWithHash.verification?.method === @@ -93,11 +92,12 @@ export const getDataFromExternalSources = ( if (isDataAuthentic(receivedData, urlDataWithHash.verification)) { return { ...dataEntry, value: receivedData }; } + throw new Error('result did not correctly validate'); + } catch (error: any) { console.error( - `GET request to ${urlDataWithHash.url} did not correctly validate`, + error, + `GET request to ${urlDataWithHash.url} (resolved as ${url})`, ); - } catch (error) { - console.error(error, `GET request to ${urlDataWithHash.url} failed`); } // Invalid data return { ...dataEntry, value: null }; 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 18/27] 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; } From cdc6c0a0f749209352d620db6d60865a276e072c Mon Sep 17 00:00:00 2001 From: Andreas Richter <708186+richtera@users.noreply.github.com> Date: Wed, 17 Jan 2024 06:58:21 -0500 Subject: [PATCH 19/27] fix: Use a single keccak function since ethereumjs converts it to a Buffer no matter what. --- src/constants/constants.ts | 20 ++- src/index.test.ts | 194 ++++++++++++++------------ src/lib/getDataFromExternalSources.ts | 13 +- src/lib/utils.test.ts | 21 +++ src/lib/utils.ts | 19 ++- 5 files changed, 154 insertions(+), 113 deletions(-) diff --git a/src/constants/constants.ts b/src/constants/constants.ts index c0cc79ca..ae7c6c43 100644 --- a/src/constants/constants.ts +++ b/src/constants/constants.ts @@ -14,6 +14,7 @@ /* eslint-disable @typescript-eslint/ban-types */ import { numberToHex, keccak256 } from 'web3-utils'; +import { arrToBufArr, bufferToHex } from 'ethereumjs-util'; import { MethodData, Encoding, Method } from '../types/Method'; @@ -112,25 +113,34 @@ export const SUPPORTED_VERIFICATION_METHODS_LIST = Object.values( SUPPORTED_VERIFICATION_METHOD_STRINGS, ); -function keccak256Utf8(data) { - return keccak256(JSON.stringify(data)); +function keccak256Method(data: object | string | Uint8Array | null) { + if (data === null) { + return keccak256(''); + } + if (data instanceof Uint8Array) { + return keccak256(bufferToHex(arrToBufArr(data))); + } + if (typeof data === 'object') { + return keccak256(JSON.stringify(data)); + } + return keccak256(data); } const KECCAK256_UTF8 = { - method: keccak256Utf8, + method: keccak256Method, name: SUPPORTED_VERIFICATION_METHOD_STRINGS.KECCAK256_UTF8, sig: SUPPORTED_VERIFICATION_METHOD_HASHES.HASH_KECCAK256_UTF8, }; const KECCAK256_BYTES = { - method: keccak256, + method: keccak256Method, name: SUPPORTED_VERIFICATION_METHOD_STRINGS.KECCAK256_BYTES, sig: SUPPORTED_VERIFICATION_METHOD_HASHES.HASH_KECCAK256_BYTES, }; export const HASH_METHODS: { [key: string]: { - method: Function; + method: (data: object | string | Uint8Array | null) => string; name: SUPPORTED_VERIFICATION_METHOD_STRINGS; sig: SUPPORTED_VERIFICATION_METHODS; }; diff --git a/src/index.test.ts b/src/index.test.ts index 8ce01ccd..2b4b2388 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -648,21 +648,23 @@ describe('Running @erc725/erc725.js tests...', () => { const jsonString = `{"LSP3Profile":{"profileImage":"ipfs://QmYo8yg4zzmdu26NSvtsoKeU5oVR6h2ohmoa2Cx5i91mPf","backgroundImage":"ipfs://QmZF5pxDJcB8eVvCd74rsXBFXhWL3S1XR5tty2cy1a58Ew","description":"Beautiful clothing that doesn't cost the Earth. A sustainable designer based in London Patrick works with brand partners to refocus on systemic change centred around creative education. "}}`; const fetchStub = sinon.stub(global, 'fetch'); - fetchStub.onCall(0).returns( - Promise.resolve( - new Response(jsonString, { - headers: { 'content-type': 'application/json' }, - }), - ), - ); - const result = await erc725.fetchData('TestJSONURL'); - fetchStub.restore(); - - assert.deepStrictEqual(result, { - key: testJSONURLSchema.key, - name: testJSONURLSchema.name, - value: JSON.parse(jsonString), - }); + try { + fetchStub.onCall(0).returns( + Promise.resolve( + new Response(jsonString, { + headers: { 'content-type': 'application/json' }, + }), + ), + ); + const result = await erc725.fetchData('TestJSONURL'); + assert.deepStrictEqual(result, { + key: testJSONURLSchema.key, + name: testJSONURLSchema.name, + value: JSON.parse(jsonString), + }); + } finally { + fetchStub.restore(); + } }); it('fetchData JSONURL with custom config.ipfsGateway', async () => { @@ -686,26 +688,29 @@ describe('Running @erc725/erc725.js tests...', () => { const jsonString = `{"LSP3Profile":{"profileImage":"ipfs://QmYo8yg4zzmdu26NSvtsoKeU5oVR6h2ohmoa2Cx5i91mPf","backgroundImage":"ipfs://QmZF5pxDJcB8eVvCd74rsXBFXhWL3S1XR5tty2cy1a58Ew","description":"Beautiful clothing that doesn't cost the Earth. A sustainable designer based in London Patrick works with brand partners to refocus on systemic change centred around creative education. "}}`; const fetchStub = sinon.stub(global, 'fetch'); - fetchStub.onCall(0).returns( - Promise.resolve( - new Response(jsonString, { - headers: { 'content-type': 'application/json' }, - }), - ), - ); - const result = await erc725.fetchData('TestJSONURL'); - assert.deepStrictEqual(result, { - key: testJSONURLSchema.key, - name: testJSONURLSchema.name, - value: JSON.parse(jsonString), - }); - fetchStub.restore(); + try { + fetchStub.onCall(0).returns( + Promise.resolve( + new Response(jsonString, { + headers: { 'content-type': 'application/json' }, + }), + ), + ); + const result = await erc725.fetchData('TestJSONURL'); + assert.deepStrictEqual(result, { + key: testJSONURLSchema.key, + name: testJSONURLSchema.name, + value: JSON.parse(jsonString), + }); - assert.ok( - fetchStub.calledWith( - `${ipfsGateway}/ipfs/QmbErKh3FjsAR6YjsTjHZNm6McDp6aRt82Ftcv9AJJvZbd`, // this value comes from the mockSchema - ), - ); + assert.ok( + fetchStub.calledWith( + `${ipfsGateway}/ipfs/QmbErKh3FjsAR6YjsTjHZNm6McDp6aRt82Ftcv9AJJvZbd`, // this value comes from the mockSchema + ), + ); + } finally { + fetchStub.restore(); + } }); if (contractVersion.interface === ERC725Y_INTERFACE_IDS['3.0']) { @@ -739,72 +744,83 @@ describe('Running @erc725/erc725.js tests...', () => { const jsonString = `{"LSP3Profile":{"profileImage":"ipfs://QmYo8yg4zzmdu26NSvtsoKeU5oVR6h2ohmoa2Cx5i91mPf","backgroundImage":"ipfs://QmZF5pxDJcB8eVvCd74rsXBFXhWL3S1XR5tty2cy1a58Ew","description":"Beautiful clothing that doesn't cost the Earth. A sustainable designer based in London Patrick works with brand partners to refocus on systemic change centred around creative education. "}}`; const fetchStub = sinon.stub(global, 'fetch'); - fetchStub - .onCall(0) - .returns(Promise.resolve(new Response(jsonString))); - const result = await erc725.fetchData({ - keyName: 'JSONForAddress:
', - dynamicKeyParts: '0xcafecafecafecafecafecafecafecafecafecafe', - }); - fetchStub.restore(); - assert.deepStrictEqual(result, { - name: 'JSONForAddress:cafecafecafecafecafecafecafecafecafecafe', - key: '0x84b02f6e50a0a0819a4f0000cafecafecafecafecafecafecafecafecafecafe', - value: JSON.parse(jsonString), - }); + try { + fetchStub + .onCall(0) + .returns(Promise.resolve(new Response(jsonString))); + const result = await erc725.fetchData({ + keyName: 'JSONForAddress:
', + dynamicKeyParts: '0xcafecafecafecafecafecafecafecafecafecafe', + }); + + assert.deepStrictEqual(result, { + name: 'JSONForAddress:cafecafecafecafecafecafecafecafecafecafe', + key: '0x84b02f6e50a0a0819a4f0000cafecafecafecafecafecafecafecafecafecafe', + value: JSON.parse(jsonString), + }); + } finally { + fetchStub.restore(); + } }); } if (contractVersion.interface === ERC725Y_INTERFACE_IDS.legacy) { it('fetchData AssetURL', async () => { const fetchStub = sinon.stub(global, 'fetch'); - fetchStub - .onCall(0) - .returns(Promise.resolve(new Response(new Uint8Array(5)))); + try { + fetchStub + .onCall(0) + .returns( + Promise.resolve( + new Response( + Uint8Array.from(Buffer.from('{"hello": "world"}')), + ), + ), + ); - const provider = new HttpProvider( - { - returnData: [ + const provider = new HttpProvider( + { + returnData: [ + { + key: '0xf18290c9b373d751e12c5ec807278267a807c35c3806255168bc48a85757ceee', + value: + '0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000598019f9b1c41589e7559804ea4a2080dad19d876a024ccb05117835447d72ce08c1d020ec697066733a2f2f516d596f387967347a7a6d647532364e537674736f4b6555356f56523668326f686d6f61324378356939316d506600000000000000', + }, + + // Encoded value of: + // { + // verification: { + // method: 'keccak256(bytes)', // 0x8019f9b1 + // data: '0xc41589e7559804ea4a2080dad19d876a024ccb05117835447d72ce08c1d020ec', + // }, + // url: 'ipfs://QmYo8yg4zzmdu26NSvtsoKeU5oVR6h2ohmoa2Cx5i91mPf', + // }, + ], + }, + [contractVersion.interface], + ); + + const erc725 = new ERC725( + [ { + name: 'TestAssetURL', key: '0xf18290c9b373d751e12c5ec807278267a807c35c3806255168bc48a85757ceee', - value: - '0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000598019f9b1c41589e7559804ea4a2080dad19d876a024ccb05117835447d72ce08c1d020ec697066733a2f2f516d596f387967347a7a6d647532364e537674736f4b6555356f56523668326f686d6f61324378356939316d506600000000000000', + keyType: 'Singleton', + valueContent: 'AssetURL', + valueType: 'bytes', }, - - // Encoded value of: - // { - // verification: { - // method: 'keccak256(bytes)', // 0x8019f9b1 - // data: '0xc41589e7559804ea4a2080dad19d876a024ccb05117835447d72ce08c1d020ec', - // }, - // url: 'ipfs://QmYo8yg4zzmdu26NSvtsoKeU5oVR6h2ohmoa2Cx5i91mPf', - // }, ], - }, - [contractVersion.interface], - ); - - const erc725 = new ERC725( - [ - { - name: 'TestAssetURL', - key: '0xf18290c9b373d751e12c5ec807278267a807c35c3806255168bc48a85757ceee', - keyType: 'Singleton', - valueContent: 'AssetURL', - valueType: 'bytes', - }, - ], - address, - provider, - ); - const result = await erc725.fetchData('TestAssetURL'); - - fetchStub.restore(); - - assert.strictEqual( - Object.prototype.toString.call(result.value), - '[object Uint8Array]', - ); + address, + provider, + ); + const result = await erc725.fetchData('TestAssetURL'); + + assert.strictEqual(result.value, { + hello: 'world', + }); + } finally { + fetchStub.restore(); + } }); } }); diff --git a/src/lib/getDataFromExternalSources.ts b/src/lib/getDataFromExternalSources.ts index 3947bea6..56ae82fd 100644 --- a/src/lib/getDataFromExternalSources.ts +++ b/src/lib/getDataFromExternalSources.ts @@ -24,7 +24,6 @@ import { GetDataExternalSourcesOutput, } from '../types/decodeData'; import { ERC725JSONSchema } from '../types/ERC725JSONSchema'; -import { SUPPORTED_VERIFICATION_METHOD_STRINGS } from '../constants/constants'; import { isDataAuthentic, patchIPFSUrlsIfApplicable } from './utils'; import { URLDataWithHash } from '../types'; @@ -84,19 +83,15 @@ export const getDataFromExternalSources = ( 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)); - } + // Previously we used to return a Uint8Array in the case of a verification + // method of 'keccak256(bytes)' but since this is a JSONURL or VerifiableURI, + // all data has to be json for sure. return response.json(); }); if (isDataAuthentic(receivedData, urlDataWithHash.verification)) { return { ...dataEntry, value: receivedData }; } + console.log(receivedData, urlDataWithHash.verification); throw new Error('result did not correctly validate'); } catch (error: any) { error.message = `GET request to ${urlDataWithHash.url} (resolved as ${url}) failed: ${error.message}`; diff --git a/src/lib/utils.test.ts b/src/lib/utils.test.ts index 89b6056b..8a8ee3f2 100644 --- a/src/lib/utils.test.ts +++ b/src/lib/utils.test.ts @@ -38,6 +38,7 @@ import { encodeTupleKeyValue, duplicateMultiTypeERC725SchemaEntry, splitMultiDynamicKeyNamePart, + countSignificantBits, } from './utils'; import { isDynamicKeyName } from './encodeKeyName'; import { decodeKey } from './decodeData'; @@ -252,6 +253,26 @@ describe('utils', () => { }); }); + describe('count bits', () => { + const testCases = [ + { + value: '0x00', + result: 0, + }, + { + value: '0x01', + result: 1, + }, + { value: '0x1000', result: 13 }, + { value: '0x000f', result: 4 }, + ]; + testCases.forEach(({ value, result }) => { + it(`should count the number of bits in ${value}`, () => { + assert.equal(countSignificantBits(value), result); + }); + }); + }); + describe('encodeKeyValue/decodeKeyValue', () => { const testCases = [ { diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 2a986237..04e4dbaf 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -28,7 +28,6 @@ import { padLeft, stripHexPrefix, } from 'web3-utils'; -import { arrToBufArr } from 'ethereumjs-util'; import { URLDataToEncode, @@ -505,17 +504,11 @@ export function isDataAuthentic( data: string | Uint8Array, options: Verification, ): boolean { - let dataHash: string; - if (!options || !options.method) { return true; } - if (data instanceof Uint8Array) { - dataHash = hashData(arrToBufArr(data), options.method); - } else { - dataHash = hashData(data, options.method); - } + const dataHash = hashData(data, options.method); if (dataHash !== options.data) { console.error( @@ -589,11 +582,17 @@ export function countNumberOfBytes(data: string) { return stripHexPrefix(data).length / 2; } -export function countSignificantBits(data = '0x0') { +export function countSignificantBits(data: string) { const bytes = hexToBytes(data); for (let i = 0; i < bytes.length; i++) { if (bytes[i] !== 0) { - return (bytes.length - i - 1) * 8 + 32 - Math.clz32(bytes[i]); + return ( + (bytes.length - i - 1) * 8 + // The number of bits with non-zero values from the right. + (32 - Math.clz32(bytes[i])) // The number of bits from the right of the current byte. + // The docs for Math.clz32 are: + // Returns the number of leading zero bits in the 32-bit binary representation of a number. + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/clz32 + ); } } return 0; From 74c0526e3f544400ced78f02ff4fed64bd048bfa Mon Sep 17 00:00:00 2001 From: Andreas Richter <708186+richtera@users.noreply.github.com> Date: Wed, 17 Jan 2024 14:38:26 -0500 Subject: [PATCH 20/27] fix: Debug and repair test scripts. Implement detecting of JSON inside of Uint8Array --- src/constants/constants.ts | 6 ++- src/index.test.ts | 4 +- src/lib/getDataFromExternalSources.ts | 61 ++++++++++++++++++++++++--- src/lib/utils.ts | 2 +- 4 files changed, 61 insertions(+), 12 deletions(-) diff --git a/src/constants/constants.ts b/src/constants/constants.ts index ae7c6c43..85e19aea 100644 --- a/src/constants/constants.ts +++ b/src/constants/constants.ts @@ -118,10 +118,12 @@ function keccak256Method(data: object | string | Uint8Array | null) { return keccak256(''); } if (data instanceof Uint8Array) { - return keccak256(bufferToHex(arrToBufArr(data))); + const buffer = bufferToHex(arrToBufArr(data)); + return keccak256(buffer); } if (typeof data === 'object') { - return keccak256(JSON.stringify(data)); + const buffer = JSON.stringify(data); + return keccak256(buffer); } return keccak256(data); } diff --git a/src/index.test.ts b/src/index.test.ts index 2b4b2388..a6ca9a5d 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -784,7 +784,7 @@ describe('Running @erc725/erc725.js tests...', () => { { key: '0xf18290c9b373d751e12c5ec807278267a807c35c3806255168bc48a85757ceee', value: - '0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000598019f9b1c41589e7559804ea4a2080dad19d876a024ccb05117835447d72ce08c1d020ec697066733a2f2f516d596f387967347a7a6d647532364e537674736f4b6555356f56523668326f686d6f61324378356939316d506600000000000000', + '0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000598019f9b1586e9b1e1681ba3ebad5ff5e6f673d3e3aa129fcdb76f92083dbc386cdde4312697066733a2f2f516d596f387967347a7a6d647532364e537674736f4b6555356f56523668326f686d6f61324378356939316d506600000000000000', }, // Encoded value of: @@ -815,7 +815,7 @@ describe('Running @erc725/erc725.js tests...', () => { ); const result = await erc725.fetchData('TestAssetURL'); - assert.strictEqual(result.value, { + assert.deepStrictEqual(result.value, { hello: 'world', }); } finally { diff --git a/src/lib/getDataFromExternalSources.ts b/src/lib/getDataFromExternalSources.ts index 56ae82fd..e2c47259 100644 --- a/src/lib/getDataFromExternalSources.ts +++ b/src/lib/getDataFromExternalSources.ts @@ -19,6 +19,9 @@ * @date 2021 */ +import { hexToUtf8, toHex } from 'web3-utils'; +import { arrToBufArr } from 'ethereumjs-util'; + import { DecodeDataOutput, GetDataExternalSourcesOutput, @@ -73,25 +76,69 @@ export const getDataFromExternalSources = ( const urlDataWithHash: URLDataWithHash = dataEntry.value as URLDataWithHash; // Type URLDataWithHash - let receivedData; const { url } = patchIPFSUrlsIfApplicable( urlDataWithHash as URLDataWithHash, ipfsGateway, ); try { - receivedData = await fetch(url).then(async (response) => { + if (/[=?/]$/.test(url)) { + // this URL is not verifiable and the URL ends with a / or ? or = meaning it's not a file + // and more likely to be some kind of directory or query BaseURI + return dataEntry; + } + const receivedData = await fetch(url).then(async (response) => { if (!response.ok) { throw new Error(response.statusText); } - // Previously we used to return a Uint8Array in the case of a verification - // method of 'keccak256(bytes)' but since this is a JSONURL or VerifiableURI, - // all data has to be json for sure. - return response.json(); + return response + .arrayBuffer() + .then((buffer) => new Uint8Array(buffer)); }); + if (receivedData.length >= 2) { + // JSON data cannot be less than 2 characters long. + try { + // - Build a string containing the first and last byte of the received data + // and try to convert it to utf8. If that succeeds then + // - check whether those could represent valid JSON data. + // - then validate the data as JSON + // - then verfiy the data against the verification method + const key = hexToUtf8( + toHex( + `0x${arrToBufArr( + new Uint8Array([ + receivedData[0], + receivedData[receivedData.length - 1], + ]), + ).toString('hex')}`, + ), + ); + // Currently not supported even though they could be added and can represent valid JSON. + // " " => JSON.stringify("") NOT SUPPORTED as valid JSON + // t or f and e => JSON.stringify(true) or JSON.stringify(false) NOT SUPPORTED as valid JSON + // 0-9 => JSON.stringify(0) integer or float (note .5 is not legitimate JSON) NOT SUPPORTED as valid JSON + // if (/^(\[\]|\{\}|(tf)e|\d\d)$/.test(key)) { + + // Check if the beginning or end are + // { and } => JSON.stringify({...}) => pretty much 100% of our JSON will be this. + // [ and ] => JSON.stringify([...]) + if (/^(\[\]|\{\})$/.test(key)) { + const json = hexToUtf8( + `0x${arrToBufArr(receivedData).toString('hex')}`, + ); + const value = JSON.parse(json); + console.log(json, value); + if (isDataAuthentic(value, urlDataWithHash.verification)) { + return { ...dataEntry, value }; + } + throw new Error('result did not correctly validate'); + } + } catch { + // ignore + } + } if (isDataAuthentic(receivedData, urlDataWithHash.verification)) { return { ...dataEntry, value: receivedData }; } - console.log(receivedData, urlDataWithHash.verification); throw new Error('result did not correctly validate'); } catch (error: any) { error.message = `GET request to ${urlDataWithHash.url} (resolved as ${url}) failed: ${error.message}`; diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 04e4dbaf..faa1d945 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -509,7 +509,7 @@ export function isDataAuthentic( } const dataHash = hashData(data, options.method); - + console.log(dataHash, options.data, dataHash === options.data, options, data); if (dataHash !== options.data) { console.error( `Hash mismatch, returned JSON hash ("${dataHash}") is different from expected hash: "${options.method}"`, From 08e017778fa42ade5386b8d9e85255225a7f3195 Mon Sep 17 00:00:00 2001 From: Andreas Richter <708186+richtera@users.noreply.github.com> Date: Wed, 17 Jan 2024 14:53:02 -0500 Subject: [PATCH 21/27] fix: BodyInit should be just string or buffer and not already Uint8Array --- src/index.test.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/index.test.ts b/src/index.test.ts index a6ca9a5d..8f1e3c31 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -770,13 +770,7 @@ describe('Running @erc725/erc725.js tests...', () => { try { fetchStub .onCall(0) - .returns( - Promise.resolve( - new Response( - Uint8Array.from(Buffer.from('{"hello": "world"}')), - ), - ), - ); + .returns(Promise.resolve(new Response('{"hello": "world"}'))); const provider = new HttpProvider( { From 0bd1349f863283cf16cc6808bef48990ec118361 Mon Sep 17 00:00:00 2001 From: Andreas Richter <708186+richtera@users.noreply.github.com> Date: Wed, 17 Jan 2024 15:13:28 -0500 Subject: [PATCH 22/27] fix: Simplify buffer to string conversion. --- src/lib/getDataFromExternalSources.ts | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/src/lib/getDataFromExternalSources.ts b/src/lib/getDataFromExternalSources.ts index e2c47259..9f968e72 100644 --- a/src/lib/getDataFromExternalSources.ts +++ b/src/lib/getDataFromExternalSources.ts @@ -19,7 +19,6 @@ * @date 2021 */ -import { hexToUtf8, toHex } from 'web3-utils'; import { arrToBufArr } from 'ethereumjs-util'; import { @@ -102,16 +101,12 @@ export const getDataFromExternalSources = ( // - check whether those could represent valid JSON data. // - then validate the data as JSON // - then verfiy the data against the verification method - const key = hexToUtf8( - toHex( - `0x${arrToBufArr( - new Uint8Array([ - receivedData[0], - receivedData[receivedData.length - 1], - ]), - ).toString('hex')}`, - ), - ); + const key = arrToBufArr( + new Uint8Array([ + receivedData[0], + receivedData[receivedData.length - 1], + ]), + ).toString(); // Currently not supported even though they could be added and can represent valid JSON. // " " => JSON.stringify("") NOT SUPPORTED as valid JSON // t or f and e => JSON.stringify(true) or JSON.stringify(false) NOT SUPPORTED as valid JSON @@ -122,9 +117,7 @@ export const getDataFromExternalSources = ( // { and } => JSON.stringify({...}) => pretty much 100% of our JSON will be this. // [ and ] => JSON.stringify([...]) if (/^(\[\]|\{\})$/.test(key)) { - const json = hexToUtf8( - `0x${arrToBufArr(receivedData).toString('hex')}`, - ); + const json = arrToBufArr(receivedData).toString(); const value = JSON.parse(json); console.log(json, value); if (isDataAuthentic(value, urlDataWithHash.verification)) { From e349d5715c7779c1eeb8d6e08c3ae3bf17eedd9e Mon Sep 17 00:00:00 2001 From: Andreas Richter <708186+richtera@users.noreply.github.com> Date: Wed, 17 Jan 2024 15:19:47 -0500 Subject: [PATCH 23/27] fix: Remove console debugging. --- src/lib/getDataFromExternalSources.ts | 1 - src/lib/utils.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/src/lib/getDataFromExternalSources.ts b/src/lib/getDataFromExternalSources.ts index 9f968e72..adf2cf43 100644 --- a/src/lib/getDataFromExternalSources.ts +++ b/src/lib/getDataFromExternalSources.ts @@ -119,7 +119,6 @@ export const getDataFromExternalSources = ( if (/^(\[\]|\{\})$/.test(key)) { const json = arrToBufArr(receivedData).toString(); const value = JSON.parse(json); - console.log(json, value); if (isDataAuthentic(value, urlDataWithHash.verification)) { return { ...dataEntry, value }; } diff --git a/src/lib/utils.ts b/src/lib/utils.ts index faa1d945..132a793f 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -509,7 +509,6 @@ export function isDataAuthentic( } const dataHash = hashData(data, options.method); - console.log(dataHash, options.data, dataHash === options.data, options, data); if (dataHash !== options.data) { console.error( `Hash mismatch, returned JSON hash ("${dataHash}") is different from expected hash: "${options.method}"`, From df2580d9434dc386480f7eec2cd6e94a5b4ca7c1 Mon Sep 17 00:00:00 2001 From: Andreas Richter <708186+richtera@users.noreply.github.com> Date: Thu, 18 Jan 2024 04:08:51 -0500 Subject: [PATCH 24/27] fix: Simplify creation of key to detect JSON. --- src/lib/getDataFromExternalSources.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/lib/getDataFromExternalSources.ts b/src/lib/getDataFromExternalSources.ts index adf2cf43..809d58c6 100644 --- a/src/lib/getDataFromExternalSources.ts +++ b/src/lib/getDataFromExternalSources.ts @@ -101,12 +101,10 @@ export const getDataFromExternalSources = ( // - check whether those could represent valid JSON data. // - then validate the data as JSON // - then verfiy the data against the verification method - const key = arrToBufArr( - new Uint8Array([ - receivedData[0], - receivedData[receivedData.length - 1], - ]), - ).toString(); + const key = String.fromCharCode( + receivedData[0], + receivedData[receivedData.length - 1], + ); // Currently not supported even though they could be added and can represent valid JSON. // " " => JSON.stringify("") NOT SUPPORTED as valid JSON // t or f and e => JSON.stringify(true) or JSON.stringify(false) NOT SUPPORTED as valid JSON From c54a370c1e4f8d71ba6bb7e62122b89069f521e6 Mon Sep 17 00:00:00 2001 From: CJ42 Date: Mon, 22 Jan 2024 16:44:40 +0000 Subject: [PATCH 25/27] feat: allow to load typed schemas from `schemas/` folder --- src/index.test.ts | 15 +++++++++++ src/schemas/index.ts | 47 ++++++++++++++++++----------------- src/types/ERC725JSONSchema.ts | 2 +- 3 files changed, 40 insertions(+), 24 deletions(-) diff --git a/src/index.test.ts b/src/index.test.ts index 8f1e3c31..c498e50f 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -24,6 +24,9 @@ import Web3 from 'web3'; import * as sinon from 'sinon'; import { hexToNumber, leftPad, numberToHex } from 'web3-utils'; +// examples of schemas to load (for testing) +import { LSP1Schema, LSP12Schema, LSP3Schema, LSP6Schema } from './schemas'; + import ERC725 from '.'; import { decodeKeyValue, @@ -68,6 +71,18 @@ describe('Running @erc725/erc725.js tests...', () => { }, /Incorrect or unsupported provider/); }); + it('should allow importing the schemas and instantiating with them', async () => { + const schemasToLoad = [ + ...LSP1Schema, + ...LSP12Schema, + ...LSP3Schema, + ...LSP6Schema, + ]; + const erc725 = new ERC725(schemasToLoad); + + assert.deepStrictEqual(erc725.options.schemas, schemasToLoad); + }); + it('should throw when calling getData without address & provider options set', async () => { const erc725 = new ERC725(mockSchema); try { diff --git a/src/schemas/index.ts b/src/schemas/index.ts index eb9e5154..2cfbfdfd 100644 --- a/src/schemas/index.ts +++ b/src/schemas/index.ts @@ -1,26 +1,27 @@ import { ERC725JSONSchema } from '../types/ERC725JSONSchema'; -import LSP1UniversalReceiverDelegate from '../../schemas/LSP1UniversalReceiverDelegate.json'; -import LSP3Profile from '../../schemas/LSP3ProfileMetadata.json'; -import LSP4DigitalAssetLegacy from '../../schemas/LSP4DigitalAssetLegacy.json'; -import LSP4DigitalAsset from '../../schemas/LSP4DigitalAsset.json'; -import LSP5ReceivedAssets from '../../schemas/LSP5ReceivedAssets.json'; -import LSP6KeyManager from '../../schemas/LSP6KeyManager.json'; -import LSP8IdentifiableDigitalAsset from '../../schemas/LSP8IdentifiableDigitalAsset.json'; -import LSP9Vault from '../../schemas/LSP9Vault.json'; -import LSP10ReceivedVaults from '../../schemas/LSP10ReceivedVaults.json'; -import LSP12IssuedAssets from '../../schemas/LSP12IssuedAssets.json'; -import LSP17ContractExtension from '../../schemas/LSP17ContractExtension.json'; +import LSP1JSONSchema from '../../schemas/LSP1UniversalReceiverDelegate.json'; +import LSP3JSONSchema from '../../schemas/LSP3ProfileMetadata.json'; +import LSP4JSONSchema from '../../schemas/LSP4DigitalAsset.json'; +import LSP4LegacyJSONSchema from '../../schemas/LSP4DigitalAssetLegacy.json'; +import LSP5JSONSchema from '../../schemas/LSP5ReceivedAssets.json'; +import LSP6JSONSchema from '../../schemas/LSP6KeyManager.json'; +import LSP8JSONSchema from '../../schemas/LSP8IdentifiableDigitalAsset.json'; +import LSP9JSONSchema from '../../schemas/LSP9Vault.json'; +import LSP10JSONSchema from '../../schemas/LSP10ReceivedVaults.json'; +import LSP12JSONSchema from '../../schemas/LSP12IssuedAssets.json'; +import LSP17JSONSchema from '../../schemas/LSP17ContractExtension.json'; -export default LSP1UniversalReceiverDelegate.concat( - LSP3Profile, - LSP4DigitalAssetLegacy, - LSP4DigitalAsset, - LSP5ReceivedAssets, - LSP6KeyManager, - LSP8IdentifiableDigitalAsset, - LSP9Vault, - LSP10ReceivedVaults, - LSP12IssuedAssets, - LSP17ContractExtension, -) as ERC725JSONSchema[]; +type schemaType = ERC725JSONSchema[]; + +export const LSP1Schema: schemaType = LSP1JSONSchema as schemaType; +export const LSP3Schema: schemaType = LSP3JSONSchema as schemaType; +export const LSP4Schema: schemaType = LSP4JSONSchema as schemaType; +export const LSP4LegacySchema: schemaType = LSP4LegacyJSONSchema as schemaType; +export const LSP5Schema: schemaType = LSP5JSONSchema as schemaType; +export const LSP6Schema: schemaType = LSP6JSONSchema as schemaType; +export const LSP8Schema: schemaType = LSP8JSONSchema as schemaType; +export const LSP9Schema: schemaType = LSP9JSONSchema as schemaType; +export const LSP10Schema: schemaType = LSP10JSONSchema as schemaType; +export const LSP12Schema: schemaType = LSP12JSONSchema as schemaType; +export const LSP17Schema: schemaType = LSP17JSONSchema as schemaType; diff --git a/src/types/ERC725JSONSchema.ts b/src/types/ERC725JSONSchema.ts index e1b6b661..5eb0eced 100644 --- a/src/types/ERC725JSONSchema.ts +++ b/src/types/ERC725JSONSchema.ts @@ -159,7 +159,7 @@ export function isValidValueType( export interface ERC725JSONSchema { name: string; // Describes the name of the key, SHOULD compromise of the Standards name + sub type. e.g: LSP2Name key: string; // The keccak256 hash of the name. This is the actual key that MUST be retrievable via ERC725Y.getData(bytes32 key) - keyType: ERC725JSONSchemaKeyType; // Types that determine how the values should be interpreted. + keyType: ERC725JSONSchemaKeyType | string; // Types that determine how the values should be interpreted. valueContent: ERC725JSONSchemaValueContent | string; // string holds '0x1345ABCD...' If the value content are specific bytes, than the returned value is expected to equal those bytes. valueType: ERC725JSONSchemaValueType | string; // The type of the value. This is used to determine how the value should be encoded / decode (`string` for tuples and CompactBytesArray). } From 8cd416bb81f959a3d367c3f45e4a7b46e184f967 Mon Sep 17 00:00:00 2001 From: Felix Hildebrandt <61689369+fhildeb@users.noreply.github.com> Date: Mon, 22 Jan 2024 17:46:56 +0100 Subject: [PATCH 26/27] docs: Add TypeScript to getting started page (#379) * docs: Add TypeScript to getting started page * Apply suggestions Co-authored-by: Hugo Masclet * add schema notice + add type to schema * apply suggestions, add changes to README --------- Co-authored-by: Hugo Masclet --- README.md | 69 ++++++++++++++++++++++++++++++++--------- docs/getting-started.md | 58 +++++++++++++++++++++++++++++----- 2 files changed, 105 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 790dd393..f4f7c7e6 100644 --- a/README.md +++ b/README.md @@ -41,44 +41,85 @@ If you install it on the backend side, you may need to also install [`isomorphic ## Instantiation -You need to initialise it with a [schema](https://docs.lukso.tech/tools/erc725js/schemas), a contract address and an RPC URL. +You need to initialise the ERC725 object with a [schema](https://docs.lukso.tech/tools/erc725js/schemas), a contract address, and an RPC URL. ```js -import { ERC725 } from '@erc725/erc725.js'; +const address = '0x0Dc07C77985fE31996Ed612F568eb441afe5768D'; +const RPC_URL = 'https://rpc.testnet.lukso.network'; +const config = { + ipfsGateway: 'https://YOUR-IPFS-GATEWAY/ipfs/', + gas: 20_000_000, // optional, default is 1_000_000 +}; +``` + +### TypeScript -// Part of LSP3-Profile Schema -// https://github.com/lukso-network/LIPs/blob/master/LSPs/LSP-3-Profile-Metadata.md -const schema = [ +> If you are using ES6 `import` statements in Node.js, make sure your file has a `.mjs` extension, or that your project is set up to support ES6 modules. + +```ts +import { ERC725, ERC725JSONSchema } from '@erc725/erc725.js'; + +// Part of LSP3-UniversalProfile Schema +// https://github.com/lukso-network/LIPs/blob/master/LSPs/LSP-3-UniversalProfile.md +const schemas: ERC725JSONSchema[] = [ { name: 'SupportedStandards:LSP3Profile', key: '0xeafec4d89fa9619884b600005ef83ad9559033e6e941db7d7c495acdce616347', keyType: 'Mapping', - valueContent: '0x5ef83ad9', valueType: 'bytes', + valueContent: '0x5ef83ad9', }, { name: 'LSP3Profile', key: '0x5ef83ad9559033e6e941db7d7c495acdce616347d28e90c7ce47cbfcfcad3bc5', keyType: 'Singleton', - valueContent: 'VerifiableURI', valueType: 'bytes', + valueContent: 'VerifiableURI', }, { name: 'LSP1UniversalReceiverDelegate', key: '0x0cfc51aec37c55a4d0b1a65c6255c4bf2fbdf6277f3cc0730c45b828b6db8b47', keyType: 'Singleton', - valueContent: 'Address', valueType: 'address', + valueContent: 'Address', }, ]; -const address = '0x3000783905Cc7170cCCe49a4112Deda952DDBe24'; -const RPC_URL = 'https://rpc.testnet.lukso.network'; -const config = { - ipfsGateway: 'https://2eff.lukso.dev/ipfs/', -}; +const erc725 = new ERC725(schemas, address, RPC_URL, config); +``` + +### JavaScript + +```js +import { ERC725 } require('@erc725/erc725.js'); + +// Part of LSP3-UniversalProfile Schema +// https://github.com/lukso-network/LIPs/blob/master/LSPs/LSP-3-UniversalProfile.md +const schemas = [ + { + name: 'SupportedStandards:LSP3Profile', + key: '0xeafec4d89fa9619884b600005ef83ad9559033e6e941db7d7c495acdce616347', + keyType: 'Mapping', + valueType: 'bytes', + valueContent: '0x5ef83ad9', + }, + { + name: 'LSP3Profile', + key: '0x5ef83ad9559033e6e941db7d7c495acdce616347d28e90c7ce47cbfcfcad3bc5', + keyType: 'Singleton', + valueType: 'bytes', + valueContent: 'VerifiableURI', + }, + { + name: 'LSP1UniversalReceiverDelegate', + key: '0x0cfc51aec37c55a4d0b1a65c6255c4bf2fbdf6277f3cc0730c45b828b6db8b47', + keyType: 'Singleton', + valueType: 'address', + valueContent: 'Address', + }, +]; -const myErc725 = new ERC725(schema, address, RPC_URL, config); +const erc725 = new ERC725(schemas, address, RPC_URL, config); ``` ## Usage diff --git a/docs/getting-started.md b/docs/getting-started.md index 896338bd..5b97cf89 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -27,12 +27,27 @@ If you install it on the backend side, you may need to also install [`isomorphic ## Instantiation +You need to initialise the ERC725 object with a [schema](https://docs.lukso.tech/tools/erc725js/schemas), a contract address, and an RPC URL. + ```js -import { ERC725 } from '@erc725/erc725.js'; +const address = '0x0Dc07C77985fE31996Ed612F568eb441afe5768D'; +const RPC_URL = 'https://rpc.testnet.lukso.network'; +const config = { + ipfsGateway: 'https://YOUR-IPFS-GATEWAY/ipfs/', + gas: 20_000_000, // optional, default is 1_000_000 +}; +``` + +### TypeScript + +> If you are using ES6 `import` statements in Node.js, make sure your file has a `.mjs` extension, or that your project is set up to support ES6 modules. + +```ts +import { ERC725, ERC725JSONSchema } from '@erc725/erc725.js'; // Part of LSP3-UniversalProfile Schema // https://github.com/lukso-network/LIPs/blob/master/LSPs/LSP-3-UniversalProfile.md -const schemas = [ +const schemas: ERC725JSONSchema[] = [ { name: 'SupportedStandards:LSP3Profile', key: '0xeafec4d89fa9619884b600005ef83ad9559033e6e941db7d7c495acdce616347', @@ -56,12 +71,39 @@ const schemas = [ }, ]; -const address = '0x0Dc07C77985fE31996Ed612F568eb441afe5768D'; -const RPC_URL = 'https://rpc.testnet.lukso.network'; -const config = { - ipfsGateway: 'https://YOUR-IPFS-GATEWAY/ipfs/', - gas: 20_000_000, // optional, default is 1_000_000 -}; +const erc725 = new ERC725(schemas, address, RPC_URL, config); +``` + +### JavaScript + +```js +import { ERC725 } require('@erc725/erc725.js'); + +// Part of LSP3-UniversalProfile Schema +// https://github.com/lukso-network/LIPs/blob/master/LSPs/LSP-3-UniversalProfile.md +const schemas = [ + { + name: 'SupportedStandards:LSP3Profile', + key: '0xeafec4d89fa9619884b600005ef83ad9559033e6e941db7d7c495acdce616347', + keyType: 'Mapping', + valueType: 'bytes', + valueContent: '0x5ef83ad9', + }, + { + name: 'LSP3Profile', + key: '0x5ef83ad9559033e6e941db7d7c495acdce616347d28e90c7ce47cbfcfcad3bc5', + keyType: 'Singleton', + valueType: 'bytes', + valueContent: 'VerifiableURI', + }, + { + name: 'LSP1UniversalReceiverDelegate', + key: '0x0cfc51aec37c55a4d0b1a65c6255c4bf2fbdf6277f3cc0730c45b828b6db8b47', + keyType: 'Singleton', + valueType: 'address', + valueContent: 'Address', + }, +]; const erc725 = new ERC725(schemas, address, RPC_URL, config); ``` From ab8af3951b1b4f85fda57abebc6895ac81b29b0b Mon Sep 17 00:00:00 2001 From: CJ42 Date: Mon, 22 Jan 2024 16:45:00 +0000 Subject: [PATCH 27/27] docs: add example on how to import schema from `./schemas/` folder --- docs/schemas.md | 26 +++++++++++++++++++++++--- src/schemas/index.ts | 15 +++++++++++++++ 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/docs/schemas.md b/docs/schemas.md index 42ecde81..2f7f0b21 100644 --- a/docs/schemas.md +++ b/docs/schemas.md @@ -20,9 +20,29 @@ _A quick reference for keys used in schema definitions can be seen below_ ## Standard LSP Schemas -The most common schemas of [LUKSO Standard Proposals](https://github.com/lukso-network/LIPs/tree/main/LSPs) are available under the [`schemas/`](https://github.com/ERC725Alliance/erc725.js/tree/develop/schemas) folder. +The most common schemas of [LUKSO Standard Proposals](https://github.com/lukso-network/LIPs/tree/main/LSPs) are available to import. These are typed automatically with the Typescript type `ERC725JSONSchema[]` for when instantiating `new ERC725(...)` from Typescript projects. -Current provided LSPs are: +```ts +import { + LSP1Schema, + LSP3Schema, + LSP4Schema, + LSP4LegacySchema, + LSP5Schema, + LSP6Schema, + LSP8Schema, + LSP9Schema, + LSP10Schema, + LSP12Schema, + LSP17Schema, +} from '@erc725/erc725.js/schemas'; + +const erc725js = new ERC725(LSP12Schema); +``` + +The raw JSON schemas are also available for import from the [`schemas/`](https://github.com/ERC725Alliance/erc725.js/tree/develop/schemas) folder. + +Current provided LSPs JSON schemas are: ``` LSP1UniversalReceiverDelegate.json @@ -38,7 +58,7 @@ LSP12IssuedAssets.json LSP17ContractExtension.json ``` -You can import them from: +You can import the raw JSON as follow: ```js import LSP3 from '@erc725/erc725.js/schemas/LSP3ProfileMetadata.json'; diff --git a/src/schemas/index.ts b/src/schemas/index.ts index 2cfbfdfd..0011630c 100644 --- a/src/schemas/index.ts +++ b/src/schemas/index.ts @@ -25,3 +25,18 @@ export const LSP9Schema: schemaType = LSP9JSONSchema as schemaType; export const LSP10Schema: schemaType = LSP10JSONSchema as schemaType; export const LSP12Schema: schemaType = LSP12JSONSchema as schemaType; export const LSP17Schema: schemaType = LSP17JSONSchema as schemaType; + +const AllSchemas = LSP1Schema.concat( + LSP3Schema, + LSP4Schema, + LSP4LegacySchema, + LSP5Schema, + LSP6Schema, + LSP8Schema, + LSP9Schema, + LSP10Schema, + LSP12Schema, + LSP17Schema, +); + +export default AllSchemas;