From 8adad51093239f4852d1bb2ed3ba0489bf8a978c Mon Sep 17 00:00:00 2001 From: Maxime Date: Thu, 22 Feb 2024 13:20:43 +0000 Subject: [PATCH 01/12] feat: add ALL_PERMISSIONS flag to encoding --- package-lock.json | 4 +- src/constants/constants.ts | 3 +- src/index.test.ts | 116 +++++++++++++++++++++++++++++++++++++ src/index.ts | 53 +++++++++++++++-- src/types/Method.ts | 1 + test/testHelpers.ts | 18 ++++++ 6 files changed, 186 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index ae367a79..d734fda1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@erc725/erc725.js", - "version": "0.17.2", + "version": "0.21.3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@erc725/erc725.js", - "version": "0.17.2", + "version": "0.21.3", "license": "Apache-2.0", "dependencies": { "add": "^2.0.6", diff --git a/src/constants/constants.ts b/src/constants/constants.ts index 85e19aea..f28a6852 100644 --- a/src/constants/constants.ts +++ b/src/constants/constants.ts @@ -179,7 +179,8 @@ export const LSP6_DEFAULT_PERMISSIONS = { DECRYPT : "0x0000000000000000000000000000000000000000000000000000000000100000", SIGN : "0x0000000000000000000000000000000000000000000000000000000000200000", EXECUTE_RELAY_CALL : "0x0000000000000000000000000000000000000000000000000000000000400000", - ERC4337_PERMISSION : "0x0000000000000000000000000000000000000000000000000000000000800000" + ERC4337_PERMISSION : "0x0000000000000000000000000000000000000000000000000000000000800000", + ALL_PERMISSIONS : "0x00000000000000000000000000000000000000000000000000000000007f3f7f" // lsp6 v0.14.0 }; export const LSP6_ALL_PERMISSIONS = diff --git a/src/index.test.ts b/src/index.test.ts index c498e50f..561480b2 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -47,6 +47,7 @@ import 'isomorphic-fetch'; import { ERC725Y_INTERFACE_IDS, + LSP6_DEFAULT_PERMISSIONS, SUPPORTED_VERIFICATION_METHOD_STRINGS, } from './constants/constants'; import { decodeKey } from './lib/decodeData'; @@ -1192,6 +1193,7 @@ describe('Running @erc725/erc725.js tests...', () => { SIGN: true, EXECUTE_RELAY_CALL: false, ERC4337_PERMISSION: false, + ALL_PERMISSIONS: false, }, hex: '0x00000000000000000000000000000000000000000000000000000000003f3f7f', }, @@ -1221,6 +1223,7 @@ describe('Running @erc725/erc725.js tests...', () => { SIGN: false, EXECUTE_RELAY_CALL: false, ERC4337_PERMISSION: false, + ALL_PERMISSIONS: false, }, hex: '0x0000000000000000000000000000000000000000000000000000000000000000', }, @@ -1250,6 +1253,7 @@ describe('Running @erc725/erc725.js tests...', () => { SIGN: true, EXECUTE_RELAY_CALL: false, ERC4337_PERMISSION: false, + ALL_PERMISSIONS: false, }, hex: '0x0000000000000000000000000000000000000000000000000000000000200a00', }, @@ -1279,6 +1283,7 @@ describe('Running @erc725/erc725.js tests...', () => { SIGN: false, EXECUTE_RELAY_CALL: false, ERC4337_PERMISSION: false, + ALL_PERMISSIONS: false, }, hex: '0x0000000000000000000000000000000000000000000000000000000000040800', }, @@ -1308,6 +1313,7 @@ describe('Running @erc725/erc725.js tests...', () => { SIGN: false, EXECUTE_RELAY_CALL: false, ERC4337_PERMISSION: false, + ALL_PERMISSIONS: false, }, hex: '0x0000000000000000000000000000000000000000000000000000000000040004', }, @@ -1337,6 +1343,7 @@ describe('Running @erc725/erc725.js tests...', () => { SIGN: false, EXECUTE_RELAY_CALL: false, ERC4337_PERMISSION: false, + ALL_PERMISSIONS: false, }, hex: '0x0000000000000000000000000000000000000000000000000000000000000a00', }, @@ -1376,6 +1383,112 @@ describe('Running @erc725/erc725.js tests...', () => { }); }); + describe('Randomized Permissions Encoding', () => { + function convertToPermissionBits(permissions: { [key: string]: string }) { + const permissionBits = {}; + Object.entries(permissions).forEach(([key, hexValue]) => { + // Convert hex to binary, then find the position of the '1' bit + const bitPosition = BigInt(hexValue).toString(2).length - 1; + permissionBits[key] = bitPosition; + }); + return permissionBits; + } + + // remove LSP6_DEFAULT_PERMISSIONS.ALL_PERMISSIONS from LSP6_DEFAULT_PERMISSIONS + const ALL_PERMISSIONS_WITHOUT_ALL_PERMISSIONS = Object.keys( + LSP6_DEFAULT_PERMISSIONS, + ).reduce((acc, key) => { + if (key !== 'ALL_PERMISSIONS') { + acc[key] = LSP6_DEFAULT_PERMISSIONS[key]; + } + return acc; + }, {}); + + // Use the function to generate permissionBits + const permissionBits = convertToPermissionBits( + ALL_PERMISSIONS_WITHOUT_ALL_PERMISSIONS, + ); + + // Function to generate a random permissions object + const generateRandomPermissions = () => { + return Object.keys(permissionBits).reduce((acc, key) => { + // Randomly assign true or false + acc[key] = Math.random() < 0.5; + return acc; + }, {}); + }; + + // Function to calculate expected hex based on permissions + const calculateExpectedHex = (permissions) => { + let basePermissions = BigInt(0); + Object.entries(permissions).forEach(([key, value]) => { + if (value) { + const bitPosition = permissionBits[key]; + basePermissions |= BigInt(1) << BigInt(bitPosition); + } + }); + // Convert to hex string, properly padded + return `0x${basePermissions.toString(16).padStart(64, '0')}`; + }; + + // Run the randomized test multiple times + const numberOfTests = 100; // Number of random tests + for (let i = 0; i < numberOfTests; i++) { + it(`Randomized test #${i + 1}`, () => { + const randomPermissions = generateRandomPermissions(); + const encoded = ERC725.encodePermissions(randomPermissions); + const expectedHex = calculateExpectedHex(randomPermissions); + assert.strictEqual( + encoded, + expectedHex, + `Failed at randomized test #${i + 1}`, + ); + }); + } + }); + + describe('all permissions', () => { + it('should encode ALL_PERMISSIONS correctly', () => { + const permissions = { ALL_PERMISSIONS: true }; + const encoded = ERC725.encodePermissions(permissions); + + assert.strictEqual( + encoded, + LSP6_DEFAULT_PERMISSIONS.ALL_PERMISSIONS, + 'Encoded permissions do not match expected value for ALL_PERMISSIONS', + ); + }); + + it('should ignore individual permissions when ALL_PERMISSIONS is set', () => { + const permissions = { + ALL_PERMISSIONS: true, + CHANGEOWNER: true, + }; + const encoded = ERC725.encodePermissions(permissions); + assert.strictEqual( + encoded, + LSP6_DEFAULT_PERMISSIONS.ALL_PERMISSIONS, + 'ALL_PERMISSIONS did not correctly encode with additional permissions', + ); + }); + it('should be able to disable permissions that are part of ALL_PERMISSIONS', () => { + const permissions = { + ALL_PERMISSIONS: true, + CHANGEOWNER: false, // Explicitly disable CHANGEOWNER + }; + + const encoded = ERC725.encodePermissions(permissions); + const decodedPermissions = ERC725.decodePermissions(encoded); + + // check that the permission is disabled + assert.strictEqual( + decodedPermissions.CHANGEOWNER, + false, + 'CHANGEOWNER permission was not correctly disabled', + ); + }); + }); + describe('decodePermissions', () => { testCases.forEach((testCase) => { it(`Decodes ${testCase.hex} permission correctly`, () => { @@ -1383,6 +1496,7 @@ describe('Running @erc725/erc725.js tests...', () => { ERC725.decodePermissions(testCase.hex), testCase.permissions, ); + assert.deepStrictEqual( erc725Instance.decodePermissions(testCase.hex), testCase.permissions, @@ -1419,6 +1533,7 @@ describe('Running @erc725/erc725.js tests...', () => { SIGN: true, EXECUTE_RELAY_CALL: true, ERC4337_PERMISSION: true, + ALL_PERMISSIONS: true, }, ); assert.deepStrictEqual( @@ -1450,6 +1565,7 @@ describe('Running @erc725/erc725.js tests...', () => { SIGN: true, EXECUTE_RELAY_CALL: true, ERC4337_PERMISSION: true, + ALL_PERMISSIONS: true, }, ); }); diff --git a/src/index.ts b/src/index.ts index d797aa50..0f38da8e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -450,13 +450,53 @@ export class ERC725 { * @returns {*} The permissions encoded as a hexadecimal string as defined by the LSP6 Standard. */ static encodePermissions(permissions: Permissions): string { - const result = Object.keys(permissions).reduce((previous, key) => { - return permissions[key] - ? previous | Number(hexToNumber(LSP6_DEFAULT_PERMISSIONS[key])) - : previous; - }, 0); + let basePermissions = BigInt(0); - return leftPad(toHex(result), 64); + // If ALL_PERMISSIONS is requested, start with that as the base + if (permissions.ALL_PERMISSIONS) { + basePermissions = BigInt( + hexToNumber(LSP6_DEFAULT_PERMISSIONS.ALL_PERMISSIONS), + ); + } + + // Explicitly add REENTRANCY, DELEGATECALL, and SUPER_DELEGATECALL if requested (they are not included in ALL_PERMISSIONS) + const additionalPermissions = [ + LSP6_DEFAULT_PERMISSIONS.REENTRANCY, + LSP6_DEFAULT_PERMISSIONS.DELEGATECALL, + LSP6_DEFAULT_PERMISSIONS.SUPER_DELEGATECALL, + ]; + additionalPermissions.forEach((permission) => { + if (permissions[permission]) { + basePermissions |= BigInt( + hexToNumber(LSP6_DEFAULT_PERMISSIONS[permission]), + ); + } + }); + + // Process each permission to potentially switch off permissions included in ALL_PERMISSIONS + Object.keys(permissions).forEach((key) => { + const permissionValue = BigInt( + hexToNumber(LSP6_DEFAULT_PERMISSIONS[key]), + ); + + if (permissions[key]) { + // If not dealing with ALL_PERMISSIONS or additional permissions, ensure they are added + if ( + !additionalPermissions.includes(key) && + key !== LSP6_DEFAULT_PERMISSIONS.ALL_PERMISSIONS + ) { + basePermissions |= permissionValue; + } + } else if ( + LSP6_DEFAULT_PERMISSIONS[key] !== + LSP6_DEFAULT_PERMISSIONS.ALL_PERMISSIONS + ) { + // If permission is set to false, remove it from the basePermissions + basePermissions &= ~permissionValue; + } + }); + // Convert the final BigInt permission value back to a hex string, properly padded + return leftPad(toHex(basePermissions.toString()), 64); } /** @@ -503,6 +543,7 @@ export class ERC725 { SIGN: false, EXECUTE_RELAY_CALL: false, ERC4337_PERMISSION: false, + ALL_PERMISSIONS: false, }; const permissionsToTest = Object.keys(LSP6_DEFAULT_PERMISSIONS); diff --git a/src/types/Method.ts b/src/types/Method.ts index 681aee85..6eb49bb9 100644 --- a/src/types/Method.ts +++ b/src/types/Method.ts @@ -48,4 +48,5 @@ export interface Permissions { SIGN?: boolean; EXECUTE_RELAY_CALL?: boolean; ERC4337_PERMISSION?: boolean; + ALL_PERMISSIONS?: boolean; } diff --git a/test/testHelpers.ts b/test/testHelpers.ts index c51c3cdd..b1547472 100644 --- a/test/testHelpers.ts +++ b/test/testHelpers.ts @@ -111,3 +111,21 @@ export function generateAllResults(schemas) { }; }); } + +export function hexToBitPositions(hexString) { + const binaryString = BigInt(hexString).toString(2); + + const bitPositions: number[] = []; + + for (let i = binaryString.length - 1; i >= 0; i--) { + // The current bit is set to 1 + if (binaryString[i] === '1') { + // Calculate the bit position. We subtract from the string's length to start counting from 0 + const bitPosition = binaryString.length - 1 - i; + bitPositions.push(bitPosition); + } + } + + // Return the array of bit positions + return bitPositions; +} From 315fa547ab8f2d25db4f771d818afeec0d324bdf Mon Sep 17 00:00:00 2001 From: Andreas Richter <708186+richtera@users.noreply.github.com> Date: Mon, 26 Feb 2024 07:36:06 -0500 Subject: [PATCH 02/12] fix: Patch full pretty printed with LF and TAB. --- src/lib/getDataFromExternalSources.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/getDataFromExternalSources.ts b/src/lib/getDataFromExternalSources.ts index 49fb121b..dd6416ff 100644 --- a/src/lib/getDataFromExternalSources.ts +++ b/src/lib/getDataFromExternalSources.ts @@ -126,7 +126,7 @@ export const getDataFromExternalSources = ( // Check if the beginning or end are // { and } => JSON.stringify({...}) => pretty much 100% of our JSON will be this. // [ and ] => JSON.stringify([...]) - if (/^(\[.*\]|\{.*\})\s*$/.test(key)) { + if (/^(\[.*\]|\{.*\})\s*$/s.test(key)) { const json = arrToBufArr(receivedData).toString(); const value = JSON.parse(json); if (isDataAuthentic(value, urlDataWithHash.verification)) { From f0f495c69e8b4768468437cad24f1b77d53a379f Mon Sep 17 00:00:00 2001 From: Felix Hildebrandt Date: Wed, 20 Mar 2024 14:16:34 +0100 Subject: [PATCH 03/12] feat: add dynamic array encoding --- src/lib/utils.ts | 52 ++++++++++++++++++++++++++++++++++++----- src/types/decodeData.ts | 2 ++ 2 files changed, 48 insertions(+), 6 deletions(-) diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 132a793f..6f2a8fe2 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -243,6 +243,8 @@ export function encodeKey( | URLDataToEncode | URLDataToEncode[] | boolean, + startingIndex = 0, + arrayLength = Array.isArray(value) ? value.length : 0, ) { // NOTE: This will not guarantee order of array as on chain. Assumes developer must set correct order @@ -260,21 +262,46 @@ export function encodeKey( return null; } + // Check if an starting index and array lenght are correct types + if ( + typeof startingIndex !== 'number' || + typeof arrayLength !== 'number' + ) { + throw new Error( + 'Invalid startingIndex or arrayLength parameters. Values must be of type number', + ); + } + + if (startingIndex < 0) { + throw new Error( + 'Invalid startingIndex parameter. Value cannot be negative.', + ); + } + + if (arrayLength < value.length) { + throw new Error( + 'Invalid arrayLength parameter. Array length must be at least as large as the number of elements of the value array.', + ); + } + const results: { key: string; value: string }[] = []; for (let index = 0; index < value.length; index++) { - const dataElement = value[index]; if (index === 0) { // This is arrayLength as the first element in the raw array // encoded as uint128 results.push({ key: schema.key, - value: encodeValueType('uint128', value.length), + // Encode the explicitly provided or default array length + value: encodeValueType('uint128', arrayLength), }); } + const elementIndex = startingIndex + index; + const dataElement = value[index]; + results.push({ - key: encodeArrayKey(schema.key, index), + key: encodeArrayKey(schema.key, elementIndex), value: encodeKeyValue( schema.valueContent, schema.valueType, @@ -439,7 +466,10 @@ export function encodeData( const dataAsArray = Array.isArray(data) ? data : [data]; return dataAsArray.reduce( - (accumulator, { keyName, value, dynamicKeyParts }) => { + ( + accumulator, + { keyName, value, dynamicKeyParts, startingIndex, arrayLength }, + ) => { let schemaElement: ERC725JSONSchema | null = null; let encodedValue; // would be nice to type this @@ -453,10 +483,20 @@ export function encodeData( } schemaElement = getSchemaElement(schema, keyName, dynamicKeyParts); - encodedValue = encodeKey(schemaElement, value); + encodedValue = encodeKey( + schemaElement, + value, + startingIndex, + arrayLength, + ); } else { schemaElement = getSchemaElement(schema, keyName); - encodedValue = encodeKey(schemaElement, value as any); + encodedValue = encodeKey( + schemaElement, + value as any, + startingIndex, + arrayLength, + ); } if (typeof encodedValue === 'string') { diff --git a/src/types/decodeData.ts b/src/types/decodeData.ts index b0e32342..b7d62c33 100644 --- a/src/types/decodeData.ts +++ b/src/types/decodeData.ts @@ -4,6 +4,8 @@ export interface DataInput { keyName: string; // can be the name or the hex/hash value; dynamicKeyParts?: string | string[]; + arrayLength?: number; + startingIndex?: number; } export interface EncodeDataInput extends DataInput { From 6590536cc01c71862d85f85c58d4f6584e22c6e1 Mon Sep 17 00:00:00 2001 From: Felix Hildebrandt Date: Wed, 20 Mar 2024 14:16:49 +0100 Subject: [PATCH 04/12] test: add dynamic array encoding tests --- src/lib/utils.test.ts | 162 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 162 insertions(+) diff --git a/src/lib/utils.test.ts b/src/lib/utils.test.ts index 8a8ee3f2..396102ef 100644 --- a/src/lib/utils.test.ts +++ b/src/lib/utils.test.ts @@ -720,6 +720,168 @@ describe('utils', () => { }); }); + describe('encodeData with custom array length and starting index', () => { + const schemas: ERC725JSONSchema[] = [ + { + name: 'AddressPermissions[]', + key: '0xdf30dba06db6a30e65354d9a64c609861f089545ca58c6b4dbe31a5f338cb0e3', + keyType: 'Array', + valueType: 'address', + valueContent: 'Address', + }, + ]; + + it('should be able to specify the array length + starting index', () => { + const encodedArraySection = encodeData( + [ + { + keyName: 'AddressPermissions[]', + value: [ + '0x983abc616f2442bab7a917e6bb8660df8b01f3bf', + '0x56ecbc104136d00eb37aa0dce60e075f10292d81', + ], + arrayLength: 23, + startingIndex: 21, + }, + ], + schemas, + ); + + // Expected result with custom startingIndex and arrayLength + const expectedResult = { + keys: [ + '0xdf30dba06db6a30e65354d9a64c609861f089545ca58c6b4dbe31a5f338cb0e3', + '0xdf30dba06db6a30e65354d9a64c6098600000000000000000000000000000015', // 21 + '0xdf30dba06db6a30e65354d9a64c6098600000000000000000000000000000016', // 22 + ], + values: [ + '0x00000000000000000000000000000017', // 23 + '0x983abc616f2442bab7a917e6bb8660df8b01f3bf', + '0x56ecbc104136d00eb37aa0dce60e075f10292d81', + ], + }; + + assert.deepStrictEqual( + encodedArraySection, + expectedResult, + 'Encoding with custom starting index and array length should match the expected result.', + ); + }); + + it('should throw if startingIndex is negative', () => { + const encodeDataWithNegativeStartingIndex = () => { + encodeData( + [ + { + keyName: 'AddressPermissions[]', + value: ['0x983abc616f2442bab7a917e6bb8660df8b01f3bf'], + arrayLength: 1, + startingIndex: -1, + }, + ], + schemas, + ); + }; + + assert.throws( + encodeDataWithNegativeStartingIndex, + /Invalid startingIndex/, + 'Should throw an error for negative startingIndex', + ); + }); + + it('should throw if arrayLength is smaller than elements in provided value array', () => { + const encodeDataWithLowerArrayLenght = () => { + encodeData( + [ + { + keyName: 'AddressPermissions[]', + value: [ + '0x983abc616f2442bab7a917e6bb8660df8b01f3bf', + '0x56ecbc104136d00eb37aa0dce60e075f10292d81', + ], + arrayLength: 1, // 2 elements + startingIndex: 0, + }, + ], + schemas, + ); + }; + + assert.throws( + encodeDataWithLowerArrayLenght, + /Invalid arrayLength/, + 'Should throw an error for arrayLength smaller than the number of provided elements', + ); + }); + + it('should start from 0 if startingIndex is not provided', () => { + const result = encodeData( + [ + { + keyName: 'AddressPermissions[]', + value: ['0x983abc616f2442bab7a917e6bb8660df8b01f3bf'], + arrayLength: 1, + }, + ], + schemas, + ); + + const expectedResult = { + keys: [ + '0xdf30dba06db6a30e65354d9a64c609861f089545ca58c6b4dbe31a5f338cb0e3', + '0xdf30dba06db6a30e65354d9a64c6098600000000000000000000000000000000', + ], + values: [ + '0x00000000000000000000000000000001', + '0x983abc616f2442bab7a917e6bb8660df8b01f3bf', + ], + }; + + assert.deepStrictEqual( + result, + expectedResult, + 'Should encode starting from index 0 if startingIndex is not provided', + ); + }); + + it('should use the number of elements in value field if arrayLength is not provided', () => { + const result = encodeData( + [ + { + keyName: 'AddressPermissions[]', + value: [ + '0x983abc616f2442bab7a917e6bb8660df8b01f3bf', + '0x56ecbc104136d00eb37aa0dce60e075f10292d81', + ], + // Not specifying arrayLength, it should default to the number of elements in the value array + startingIndex: 0, + }, + ], + schemas, + ); + + const expectedResult = { + keys: [ + '0xdf30dba06db6a30e65354d9a64c609861f089545ca58c6b4dbe31a5f338cb0e3', + '0xdf30dba06db6a30e65354d9a64c6098600000000000000000000000000000000', + '0xdf30dba06db6a30e65354d9a64c6098600000000000000000000000000000001', + ], + values: [ + '0x00000000000000000000000000000002', + '0x983abc616f2442bab7a917e6bb8660df8b01f3bf', + '0x56ecbc104136d00eb37aa0dce60e075f10292d81', + ], + }; + + assert.deepStrictEqual( + result, + expectedResult, + 'should use the number of elements in value field if arrayLength is not provided', + ); + }); + }); + describe('isDataAuthentic', () => { it('returns true if data is authentic', () => { const data = 'h3ll0HowAreYou?'; From 329d886f77d2fb98ef15a788778437f8d3852909 Mon Sep 17 00:00:00 2001 From: Felix Hildebrandt Date: Wed, 20 Mar 2024 18:43:41 +0100 Subject: [PATCH 05/12] fix typos --- src/lib/encoder.test.ts | 10 +++++----- src/lib/utils.test.ts | 4 ++-- src/lib/utils.ts | 3 +-- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/lib/encoder.test.ts b/src/lib/encoder.test.ts index 07e40637..ed170fca 100644 --- a/src/lib/encoder.test.ts +++ b/src/lib/encoder.test.ts @@ -633,7 +633,7 @@ describe('encoder', () => { }); }); - describe('when encoding a value that exceeds the maximal lenght of bytes than its type', () => { + describe('when encoding a value that exceeds the maximal length of bytes than its type', () => { const validTestCases = [ { valueType: 'bytes32', @@ -796,13 +796,13 @@ describe('encoder', () => { }); describe('when encoding uintN[CompactBytesArray]', () => { - it('should throw if trying to encode a value that exceeds the maximal lenght of bytes for this type', async () => { + it('should throw if trying to encode a value that exceeds the maximal length of bytes for this type', async () => { expect(() => { encodeValueType('uint8[CompactBytesArray]', [15, 178, 266]); }).to.throw('Hex uint8 value at index 2 does not fit in 1 bytes'); }); - it('should throw if trying to decode a value that exceeds the maximal lenght of bytes for this type', async () => { + it('should throw if trying to decode a value that exceeds the maximal length of bytes for this type', async () => { expect(() => { decodeValueType( 'uint8[CompactBytesArray]', @@ -813,7 +813,7 @@ describe('encoder', () => { }); describe('when encoding bytesN[CompactBytesArray]', () => { - it('should throw if trying to encode a value that exceeds the maximal lenght of bytes for this type', async () => { + it('should throw if trying to encode a value that exceeds the maximal length of bytes for this type', async () => { expect(() => { encodeValueType('bytes4[CompactBytesArray]', [ '0xe6520726', @@ -824,7 +824,7 @@ describe('encoder', () => { }).to.throw('Hex bytes4 value at index 3 does not fit in 4 bytes'); }); - it('should throw if trying to decode a value that exceeds the maximal lenght of bytes for this type', async () => { + it('should throw if trying to decode a value that exceeds the maximal length of bytes for this type', async () => { expect(() => { decodeValueType( 'bytes4[CompactBytesArray]', diff --git a/src/lib/utils.test.ts b/src/lib/utils.test.ts index 396102ef..c6184eca 100644 --- a/src/lib/utils.test.ts +++ b/src/lib/utils.test.ts @@ -791,7 +791,7 @@ describe('utils', () => { }); it('should throw if arrayLength is smaller than elements in provided value array', () => { - const encodeDataWithLowerArrayLenght = () => { + const encodeDataWithLowerArrayLength = () => { encodeData( [ { @@ -809,7 +809,7 @@ describe('utils', () => { }; assert.throws( - encodeDataWithLowerArrayLenght, + encodeDataWithLowerArrayLength, /Invalid arrayLength/, 'Should throw an error for arrayLength smaller than the number of provided elements', ); diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 6f2a8fe2..c6ddc33e 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -262,13 +262,12 @@ export function encodeKey( return null; } - // Check if an starting index and array lenght are correct types if ( typeof startingIndex !== 'number' || typeof arrayLength !== 'number' ) { throw new Error( - 'Invalid startingIndex or arrayLength parameters. Values must be of type number', + 'Invalid startingIndex or arrayLength parameters. Values must be of type number.', ); } From c46f44f8164ee781c7d426b394d2ff44dbb19beb Mon Sep 17 00:00:00 2001 From: Felix Hildebrandt Date: Wed, 20 Mar 2024 18:44:10 +0100 Subject: [PATCH 06/12] add parameter passdown test for startingIndex and arrayLength --- src/index.test.ts | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/index.test.ts b/src/index.test.ts index 561480b2..ac613cc3 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -1004,6 +1004,45 @@ describe('Running @erc725/erc725.js tests...', () => { assert.deepStrictEqual(results, intendedResult); }); + it('encodes dynamic data values for keyType "Array" in naked class instance', () => { + const schemas: ERC725JSONSchema[] = [ + { + name: 'AddressPermissions[]', + key: '0xdf30dba06db6a30e65354d9a64c609861f089545ca58c6b4dbe31a5f338cb0e3', + keyType: 'Array', + valueType: 'address', + valueContent: 'Address', + }, + ]; + const erc725 = new ERC725(schemas); + const encodedArraySection = erc725.encodeData([ + { + keyName: 'AddressPermissions[]', + value: [ + '0x983abc616f2442bab7a917e6bb8660df8b01f3bf', + '0x56ecbc104136d00eb37aa0dce60e075f10292d81', + ], + arrayLength: 23, + startingIndex: 21, + }, + ]); + + // Expected result with custom startingIndex and arrayLength + const expectedResult = { + keys: [ + '0xdf30dba06db6a30e65354d9a64c609861f089545ca58c6b4dbe31a5f338cb0e3', + '0xdf30dba06db6a30e65354d9a64c6098600000000000000000000000000000015', // 21 + '0xdf30dba06db6a30e65354d9a64c6098600000000000000000000000000000016', // 22 + ], + values: [ + '0x00000000000000000000000000000017', // 23 + '0x983abc616f2442bab7a917e6bb8660df8b01f3bf', + '0x56ecbc104136d00eb37aa0dce60e075f10292d81', + ], + }; + assert.deepStrictEqual(encodedArraySection, expectedResult); + }); + it(`decode all data values for keyType "Array" in naked class instance: ${schemaElement.name}`, async () => { const values = allGraphData.filter( (e) => e.key.slice(0, 34) === schemaElement.key.slice(0, 34), From ec79a4a8cb58df4fb518208ba2450413f1dd4f9e Mon Sep 17 00:00:00 2001 From: Felix Hildebrandt Date: Wed, 20 Mar 2024 18:47:03 +0100 Subject: [PATCH 07/12] update documentation --- docs/classes/ERC725.md | 48 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/docs/classes/ERC725.md b/docs/classes/ERC725.md index d26b5c78..a8b85885 100644 --- a/docs/classes/ERC725.md +++ b/docs/classes/ERC725.md @@ -449,6 +449,8 @@ An array of objects containing the following properties: | `keyName` | string | Can be either the named key (i.e. `LSP3Profile`, `LSP12IssuedAssetsMap:
`) or the hashed key (with or without `0x` prefix, i.e. `0x5ef...` or `5ef...`). | | `dynamicKeyParts` (optional) | string or
string[ ] | The dynamic parts of the `keyName` that will be used for encoding the key. | | `value` | string or
string[ ]
JSON todo | The value that should be encoded. Can be a string, an array of string or a JSON... | +| `startingIndex` (optional) | number | Starting index for `Array` types to encode a subset of elements. Defaults t `0`. | +| `arrayLength` (optional) | number | Parameter for `Array` types, specifying the total length when encoding a subset of elements. Defaults to the number of elements in the `value` field. | The `keyName` also supports dynamic keys for [`Mapping`](https://github.com/lukso-network/LIPs/blob/main/LSPs/LSP-2-ERC725YJSONSchema.md#mapping) and [`MappingWithGrouping`](https://github.com/lukso-network/LIPs/blob/main/LSPs/LSP-2-ERC725YJSONSchema.md#mapping). Therefore, you can use variables in the key name such as `LSP12IssuedAssetsMap:
`. In that case, the value should also set the `dynamicKeyParts` property: @@ -700,6 +702,50 @@ myErc725.encodeData([ +
+ Encode a subset of array elements + +```javascript title="Encode a subset of array elements" +const schemas = [ + { + name: 'AddressPermissions[]', + key: '0xdf30dba06db6a30e65354d9a64c609861f089545ca58c6b4dbe31a5f338cb0e3', + keyType: 'Array', + valueType: 'address', + valueContent: 'Address', + }, +]; + +const myErc725 = new ERC725(schemas); +myErc725.encodeData([ + { + keyName: 'AddressPermissions[]', + value: [ + '0x983abc616f2442bab7a917e6bb8660df8b01f3bf', + '0x56ecbc104136d00eb37aa0dce60e075f10292d81', + ], + arrayLength: 23, + startingIndex: 21, + }, +]); +/** +{ + keys: [ + '0xdf30dba06db6a30e65354d9a64c609861f089545ca58c6b4dbe31a5f338cb0e3', + '0xdf30dba06db6a30e65354d9a64c6098600000000000000000000000000000015', + '0xdf30dba06db6a30e65354d9a64c6098600000000000000000000000000000016', + ], + values: [ + '0x00000000000000000000000000000017', + '0x983abc616f2442bab7a917e6bb8660df8b01f3bf', + '0x56ecbc104136d00eb37aa0dce60e075f10292d81', + ], +} +*/ +``` + +
+ --- ## encodePermissions @@ -1611,7 +1657,7 @@ Either a string of the hexadecimal `interfaceID` as defined by [ERC165](https:// The `interfaceName` will only check for the latest version of the standard's `interfaceID`, which can be found in `src/constants/interfaces`. For LSPs, the `interfaceIDs` are taken from the latest release of the [@lukso/lsp-smart-contracts](https://github.com/lukso-network/lsp-smart-contracts) library. -:::info +::: ##### 2. `options` - Object (optional) From 0fea6428318369b58f3b506e238a3d1e16744649 Mon Sep 17 00:00:00 2001 From: Felix Hildebrandt Date: Thu, 21 Mar 2024 16:34:41 +0100 Subject: [PATCH 08/12] update example + rename arrayLength to totalArrayLength --- docs/classes/ERC725.md | 40 +++++++++++++++++++++------------------- src/index.test.ts | 4 ++-- src/lib/utils.test.ts | 26 +++++++++++++------------- src/lib/utils.ts | 20 ++++++++++---------- src/types/decodeData.ts | 2 +- 5 files changed, 47 insertions(+), 45 deletions(-) diff --git a/docs/classes/ERC725.md b/docs/classes/ERC725.md index a8b85885..68dff2be 100644 --- a/docs/classes/ERC725.md +++ b/docs/classes/ERC725.md @@ -444,13 +444,13 @@ _Example 2: input `1122334455` encoded as `bytes4` --> will encode as `0x42e576f An array of objects containing the following properties: -| Name | Type | Description | -| :--------------------------- | :--------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `keyName` | string | Can be either the named key (i.e. `LSP3Profile`, `LSP12IssuedAssetsMap:
`) or the hashed key (with or without `0x` prefix, i.e. `0x5ef...` or `5ef...`). | -| `dynamicKeyParts` (optional) | string or
string[ ] | The dynamic parts of the `keyName` that will be used for encoding the key. | -| `value` | string or
string[ ]
JSON todo | The value that should be encoded. Can be a string, an array of string or a JSON... | -| `startingIndex` (optional) | number | Starting index for `Array` types to encode a subset of elements. Defaults t `0`. | -| `arrayLength` (optional) | number | Parameter for `Array` types, specifying the total length when encoding a subset of elements. Defaults to the number of elements in the `value` field. | +| Name | Type | Description | +| :---------------------------- | :--------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `keyName` | string | Can be either the named key (i.e. `LSP3Profile`, `LSP12IssuedAssetsMap:
`) or the hashed key (with or without `0x` prefix, i.e. `0x5ef...` or `5ef...`). | +| `dynamicKeyParts` (optional) | string or
string[ ] | The dynamic parts of the `keyName` that will be used for encoding the key. | +| `value` | string or
string[ ]
JSON todo | The value that should be encoded. Can be a string, an array of string or a JSON... | +| `startingIndex` (optional) | number | Starting index for `Array` types to encode a subset of elements. Defaults t `0`. | +| `totalArrayLength` (optional) | number | Parameter for `Array` types, specifying the total length when encoding a subset of elements. Defaults to the number of elements in the `value` field. | The `keyName` also supports dynamic keys for [`Mapping`](https://github.com/lukso-network/LIPs/blob/main/LSPs/LSP-2-ERC725YJSONSchema.md#mapping) and [`MappingWithGrouping`](https://github.com/lukso-network/LIPs/blob/main/LSPs/LSP-2-ERC725YJSONSchema.md#mapping). Therefore, you can use variables in the key name such as `LSP12IssuedAssetsMap:
`. In that case, the value should also set the `dynamicKeyParts` property: @@ -716,18 +716,20 @@ const schemas = [ }, ]; -const myErc725 = new ERC725(schemas); -myErc725.encodeData([ - { - keyName: 'AddressPermissions[]', - value: [ - '0x983abc616f2442bab7a917e6bb8660df8b01f3bf', - '0x56ecbc104136d00eb37aa0dce60e075f10292d81', - ], - arrayLength: 23, - startingIndex: 21, - }, -]); +myErc725.encodeData( + [ + { + keyName: 'AddressPermissions[]', + value: [ + '0x983abc616f2442bab7a917e6bb8660df8b01f3bf', + '0x56ecbc104136d00eb37aa0dce60e075f10292d81', + ], + totalArrayLength: 23, + startingIndex: 21, + }, + ], + schemas, +); /** { keys: [ diff --git a/src/index.test.ts b/src/index.test.ts index ac613cc3..de108e06 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -1022,12 +1022,12 @@ describe('Running @erc725/erc725.js tests...', () => { '0x983abc616f2442bab7a917e6bb8660df8b01f3bf', '0x56ecbc104136d00eb37aa0dce60e075f10292d81', ], - arrayLength: 23, + totalArrayLength: 23, startingIndex: 21, }, ]); - // Expected result with custom startingIndex and arrayLength + // Expected result with custom startingIndex and totalArrayLength const expectedResult = { keys: [ '0xdf30dba06db6a30e65354d9a64c609861f089545ca58c6b4dbe31a5f338cb0e3', diff --git a/src/lib/utils.test.ts b/src/lib/utils.test.ts index c6184eca..d6d9ae75 100644 --- a/src/lib/utils.test.ts +++ b/src/lib/utils.test.ts @@ -740,14 +740,14 @@ describe('utils', () => { '0x983abc616f2442bab7a917e6bb8660df8b01f3bf', '0x56ecbc104136d00eb37aa0dce60e075f10292d81', ], - arrayLength: 23, + totalArrayLength: 23, startingIndex: 21, }, ], schemas, ); - // Expected result with custom startingIndex and arrayLength + // Expected result with custom startingIndex and totalArrayLength const expectedResult = { keys: [ '0xdf30dba06db6a30e65354d9a64c609861f089545ca58c6b4dbe31a5f338cb0e3', @@ -775,7 +775,7 @@ describe('utils', () => { { keyName: 'AddressPermissions[]', value: ['0x983abc616f2442bab7a917e6bb8660df8b01f3bf'], - arrayLength: 1, + totalArrayLength: 1, startingIndex: -1, }, ], @@ -790,8 +790,8 @@ describe('utils', () => { ); }); - it('should throw if arrayLength is smaller than elements in provided value array', () => { - const encodeDataWithLowerArrayLength = () => { + it('should throw if totalArrayLength is smaller than elements in provided value array', () => { + const encodeDataWithLowerTotalArrayLength = () => { encodeData( [ { @@ -800,7 +800,7 @@ describe('utils', () => { '0x983abc616f2442bab7a917e6bb8660df8b01f3bf', '0x56ecbc104136d00eb37aa0dce60e075f10292d81', ], - arrayLength: 1, // 2 elements + totalArrayLength: 1, // 2 elements startingIndex: 0, }, ], @@ -809,9 +809,9 @@ describe('utils', () => { }; assert.throws( - encodeDataWithLowerArrayLength, - /Invalid arrayLength/, - 'Should throw an error for arrayLength smaller than the number of provided elements', + encodeDataWithLowerTotalArrayLength, + /Invalid totalArrayLength/, + 'Should throw an error for totalArrayLength smaller than the number of provided elements', ); }); @@ -821,7 +821,7 @@ describe('utils', () => { { keyName: 'AddressPermissions[]', value: ['0x983abc616f2442bab7a917e6bb8660df8b01f3bf'], - arrayLength: 1, + totalArrayLength: 1, }, ], schemas, @@ -845,7 +845,7 @@ describe('utils', () => { ); }); - it('should use the number of elements in value field if arrayLength is not provided', () => { + it('should use the number of elements in value field if totalArrayLength is not provided', () => { const result = encodeData( [ { @@ -854,7 +854,7 @@ describe('utils', () => { '0x983abc616f2442bab7a917e6bb8660df8b01f3bf', '0x56ecbc104136d00eb37aa0dce60e075f10292d81', ], - // Not specifying arrayLength, it should default to the number of elements in the value array + // Not specifying totalArrayLength, it should default to the number of elements in the value array startingIndex: 0, }, ], @@ -877,7 +877,7 @@ describe('utils', () => { assert.deepStrictEqual( result, expectedResult, - 'should use the number of elements in value field if arrayLength is not provided', + 'should use the number of elements in value field if totalArrayLength is not provided', ); }); }); diff --git a/src/lib/utils.ts b/src/lib/utils.ts index c6ddc33e..d7bbb4a7 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -244,7 +244,7 @@ export function encodeKey( | URLDataToEncode[] | boolean, startingIndex = 0, - arrayLength = Array.isArray(value) ? value.length : 0, + totalArrayLength = Array.isArray(value) ? value.length : 0, ) { // NOTE: This will not guarantee order of array as on chain. Assumes developer must set correct order @@ -264,10 +264,10 @@ export function encodeKey( if ( typeof startingIndex !== 'number' || - typeof arrayLength !== 'number' + typeof totalArrayLength !== 'number' ) { throw new Error( - 'Invalid startingIndex or arrayLength parameters. Values must be of type number.', + 'Invalid startingIndex or totalArrayLength parameters. Values must be of type number.', ); } @@ -277,9 +277,9 @@ export function encodeKey( ); } - if (arrayLength < value.length) { + if (totalArrayLength < value.length) { throw new Error( - 'Invalid arrayLength parameter. Array length must be at least as large as the number of elements of the value array.', + 'Invalid totalArrayLength parameter. Array length must be at least as large as the number of elements of the value array.', ); } @@ -287,12 +287,12 @@ export function encodeKey( for (let index = 0; index < value.length; index++) { if (index === 0) { - // This is arrayLength as the first element in the raw array + // This is totalArrayLength as the first element in the raw array // encoded as uint128 results.push({ key: schema.key, // Encode the explicitly provided or default array length - value: encodeValueType('uint128', arrayLength), + value: encodeValueType('uint128', totalArrayLength), }); } @@ -467,7 +467,7 @@ export function encodeData( return dataAsArray.reduce( ( accumulator, - { keyName, value, dynamicKeyParts, startingIndex, arrayLength }, + { keyName, value, dynamicKeyParts, startingIndex, totalArrayLength }, ) => { let schemaElement: ERC725JSONSchema | null = null; let encodedValue; // would be nice to type this @@ -486,7 +486,7 @@ export function encodeData( schemaElement, value, startingIndex, - arrayLength, + totalArrayLength, ); } else { schemaElement = getSchemaElement(schema, keyName); @@ -494,7 +494,7 @@ export function encodeData( schemaElement, value as any, startingIndex, - arrayLength, + totalArrayLength, ); } diff --git a/src/types/decodeData.ts b/src/types/decodeData.ts index b7d62c33..7e3179f3 100644 --- a/src/types/decodeData.ts +++ b/src/types/decodeData.ts @@ -4,7 +4,7 @@ export interface DataInput { keyName: string; // can be the name or the hex/hash value; dynamicKeyParts?: string | string[]; - arrayLength?: number; + totalArrayLength?: number; startingIndex?: number; } From 740e5a1f41a53d5c65e5a0d562876febebb40392 Mon Sep 17 00:00:00 2001 From: Felix Hildebrandt Date: Thu, 21 Mar 2024 17:07:29 +0100 Subject: [PATCH 09/12] add further parameter explanation and warning --- docs/classes/ERC725.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/docs/classes/ERC725.md b/docs/classes/ERC725.md index 68dff2be..b2194955 100644 --- a/docs/classes/ERC725.md +++ b/docs/classes/ERC725.md @@ -456,6 +456,24 @@ The `keyName` also supports dynamic keys for [`Mapping`](https://github.com/luks - `dynamicKeyParts`: string or string[ ] which holds the variables that needs to be encoded. +:::info Handling array subsets + +The `totalArrayLength` parameter must be explicitly provided to ensure integrity when encoding subsets or modifying existing array elements. Its value specifies the total length of the array **after the operation is completed**, not just the size of the encoded subset. + +**When to Use `totalArrayLength`** + +- **Adding Elements:** When adding new elements to an array, `totalArrayLength` should equal the sum of the current array's length plus the number of new elements added. +- **Modifying Elements:** If modifying elements within an existing array without changing the total number of elements, `totalArrayLength` should match the previous length of the array. +- **Removing Elements:** In cases where elements are removed, `totalArrayLength` should reflect the number of elements left. + +::: + +:::caution Encoding array lengths + +Please be careful when updating existing contract data. Incorrect usage of `startingIndex` and `totalArrayLength` can lead to improperly encoded data that changes the intended structure of the data field. + +::: + ##### 2. `schemas` - Array of Objects (optional) An array of extra [LSP-2 ERC725YJSONSchema] objects that can be used to find the schema. If called on an instance, it is optional and it will be concatenated with the schema provided on instantiation. From 72705935ef017937087fab20001bbbafc513b9ab Mon Sep 17 00:00:00 2001 From: Felix Hildebrandt Date: Thu, 21 Mar 2024 18:45:21 +0100 Subject: [PATCH 10/12] improve test name and error outputs --- src/index.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.test.ts b/src/index.test.ts index de108e06..db99edaf 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -1004,7 +1004,7 @@ describe('Running @erc725/erc725.js tests...', () => { assert.deepStrictEqual(results, intendedResult); }); - it('encodes dynamic data values for keyType "Array" in naked class instance', () => { + it('encodes subset of elements for keyType "Array" in naked class instance', () => { const schemas: ERC725JSONSchema[] = [ { name: 'AddressPermissions[]', From b1911cada6fb6d64fa413200da40957d1c51e474 Mon Sep 17 00:00:00 2001 From: Felix Hildebrandt Date: Fri, 22 Mar 2024 11:21:45 +0100 Subject: [PATCH 11/12] update error messages with code styling --- src/lib/utils.test.ts | 4 ++-- src/lib/utils.ts | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/lib/utils.test.ts b/src/lib/utils.test.ts index d6d9ae75..27a75b2a 100644 --- a/src/lib/utils.test.ts +++ b/src/lib/utils.test.ts @@ -785,7 +785,7 @@ describe('utils', () => { assert.throws( encodeDataWithNegativeStartingIndex, - /Invalid startingIndex/, + /Invalid `startingIndex`/, 'Should throw an error for negative startingIndex', ); }); @@ -810,7 +810,7 @@ describe('utils', () => { assert.throws( encodeDataWithLowerTotalArrayLength, - /Invalid totalArrayLength/, + /Invalid `totalArrayLength`/, 'Should throw an error for totalArrayLength smaller than the number of provided elements', ); }); diff --git a/src/lib/utils.ts b/src/lib/utils.ts index d7bbb4a7..a447f0b0 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -267,19 +267,19 @@ export function encodeKey( typeof totalArrayLength !== 'number' ) { throw new Error( - 'Invalid startingIndex or totalArrayLength parameters. Values must be of type number.', + 'Invalid `startingIndex` or `totalArrayLength` parameters. Values must be of type number.', ); } if (startingIndex < 0) { throw new Error( - 'Invalid startingIndex parameter. Value cannot be negative.', + 'Invalid `startingIndex` parameter. Value cannot be negative.', ); } if (totalArrayLength < value.length) { throw new Error( - 'Invalid totalArrayLength parameter. Array length must be at least as large as the number of elements of the value array.', + 'Invalid `totalArrayLength` parameter. Array length must be at least as large as the number of elements of the value array.', ); } From a9fdb4daa69e2aac38aca2393f94e8fe83113656 Mon Sep 17 00:00:00 2001 From: CJ42 Date: Wed, 27 Mar 2024 19:42:36 +0000 Subject: [PATCH 12/12] feat: replace dynamic part with hex value when parsing schemas for `Mapping` keys --- src/lib/schemaParser.test.ts | 38 ++++++++++++++++++++++++++++++++++++ src/lib/schemaParser.ts | 25 ++++++++++++++++++++++-- 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/src/lib/schemaParser.test.ts b/src/lib/schemaParser.test.ts index 0c6482d7..21a1f370 100644 --- a/src/lib/schemaParser.test.ts +++ b/src/lib/schemaParser.test.ts @@ -109,6 +109,7 @@ describe('schemaParser getSchema', () => { valueType: 'bytes4', }); }); + it('finds Known Mapping:
', () => { const address = 'af3bf2ffb025098b79caddfbdd113b3681817744'; const name = `MyCoolAddress:${address}`; @@ -126,6 +127,43 @@ describe('schemaParser getSchema', () => { assert.deepStrictEqual(schema, extraSchema); }); + + it('finds known SomeBytes32Mapping:', () => { + const bytes32Value = + '1111222233334444555566667777888899990000aaaabbbbccccddddeeeeffff'; + const name = `SomeBytes32Mapping:${bytes32Value}`; + const key = `0x0cfc51aec37c55a4d0b10000${bytes32Value.slice(0, 42)}`; + + const extraSchema: ERC725JSONSchema = { + name, + key, + keyType: 'Mapping', + valueContent: 'Address', + valueType: 'address', + }; + + const schema = getSchema(key, [extraSchema]); + + assert.deepStrictEqual(schema, extraSchema); + }); + + it('finds known SomeSelectorMap:', () => { + const bytes4Value = 'beefbeef'; + const name = `SomeSelectorMap:${bytes4Value}`; + const key = `0x0cfc51aec37c55a4d0b10000${bytes4Value}00000000000000000000000000000000`; + + const extraSchema: ERC725JSONSchema = { + name, + key, + keyType: 'Mapping', + valueContent: '(Address,bool)', + valueType: '(address,bool)', + }; + + const schema = getSchema(key, [extraSchema]); + + assert.deepStrictEqual(schema, extraSchema); + }); }); describe('MappingWithGrouping', () => { diff --git a/src/lib/schemaParser.ts b/src/lib/schemaParser.ts index b38a0f7b..2d7ae125 100644 --- a/src/lib/schemaParser.ts +++ b/src/lib/schemaParser.ts @@ -17,12 +17,14 @@ * @date 2022 */ +import { keccak256 } from 'web3-utils'; import allSchemas from '../schemas'; import { ERC725JSONSchema, ERC725JSONSchemaKeyType, } from '../types/ERC725JSONSchema'; +import { isDynamicKeyName } from './encodeKeyName'; const getSchemasByKeyType = ( schemas: ERC725JSONSchema[], @@ -86,6 +88,9 @@ const findMappingSchemaForKey = ( key: string, schemas: ERC725JSONSchema[], ): ERC725JSONSchema | null => { + const firstWordHex = key.substring(0, 26); + const secondWordHex = key.substring(26); + // Should detect: // 1. Known/defined mapping @@ -96,19 +101,35 @@ const findMappingSchemaForKey = ( } // 2. "Semi defined mappings" i.e. "SupportedStandards:??????" + let dynamicPart = '??????'; // default for "unknown" + keySchema = schemas.find( - (schema) => `${schema.key.substring(0, 22)}0000` === key.substring(0, 26), + (schema) => `${schema.key.substring(0, 22)}0000` === firstWordHex, ) || null; if (!keySchema) { return null; } + + const keyNameParts = keySchema.name.split(':'); + + // replace dynamic placeholder in the map part (e.g:
, ) with the hex value + if (isDynamicKeyName(keySchema.name)) { + dynamicPart = secondWordHex; + } + + // if first 20 bytes of the hash of second word in schema match, + // display the map part as plain word + if (keccak256(keyNameParts[1]).substring(0, 26) === secondWordHex) { + [, dynamicPart] = keyNameParts; + } + // TODO: Handle the SupportedStandard Keys; we can get the valueContent from the Keys return { ...keySchema, valueContent: '?', - name: `${keySchema.name.split(':')[0]}:??????`, + name: `${keyNameParts[0]}:${dynamicPart}`, key, }; };