From 49ede8df4ab65d9111017fdc5e4e671ba8aa15b8 Mon Sep 17 00:00:00 2001 From: Andreas Richter <708186+richtera@users.noreply.github.com> Date: Fri, 28 Jun 2024 21:31:39 -0400 Subject: [PATCH] fix: Implement tests and changes to make them work --- src/index.test.ts | 38 +++++++++--- src/lib/decodeData.test.ts | 10 +-- src/lib/decodeData.ts | 10 ++- src/lib/decodeMappingKey.test.ts | 30 +++++---- src/lib/decodeMappingKey.ts | 34 +++++++---- src/lib/encodeKeyName.test.ts | 50 +++++++-------- src/lib/encodeKeyName.ts | 101 ++++++++++++++++++++++++++++--- src/lib/encoder.ts | 7 +-- src/lib/getSchemaElement.test.ts | 5 +- src/lib/getSchemaElement.ts | 29 ++++++++- src/lib/schemaParser.test.ts | 2 +- src/lib/schemaParser.ts | 3 +- src/lib/utils.test.ts | 10 +-- src/lib/utils.ts | 8 +-- src/types/ERC725JSONSchema.ts | 4 ++ src/types/decodeData.ts | 4 ++ test/mockSchema.ts | 35 ++++++++++- test/testHelpers.ts | 6 +- 18 files changed, 281 insertions(+), 105 deletions(-) diff --git a/src/index.test.ts b/src/index.test.ts index 3a0fef4d..e99cc3df 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -223,6 +223,7 @@ describe('Running @erc725/erc725.js tests...', () => { name: 'ThisKeyDoesNotExist', key: '0xb12a0af5f83066646eb63c96bf29dcb827024d9a33189f5a61652a03951d1fbe', value: null, + nonDynamicName: 'ThisKeyDoesNotExist', }; assert.deepStrictEqual(data, expectedResult); @@ -240,6 +241,7 @@ describe('Running @erc725/erc725.js tests...', () => { keyType: 'Array', valueContent: 'Address', valueType: 'address', + nonDynamicName: 'NonExistingArray[]', }, ], ERC725_CONTRACT_ADDRESS, @@ -251,6 +253,7 @@ describe('Running @erc725/erc725.js tests...', () => { name: 'NonExistingArray[]', key: '0xd6cbdbfc8d25c9ce4720b5fe6fa8fc536803944271617bf5425b4bd579195840', value: [], + nonDynamicName: 'NonExistingArray[]', }); const dataArray = await erc725.getData(['NonExistingArray[]']); @@ -259,6 +262,7 @@ describe('Running @erc725/erc725.js tests...', () => { name: 'NonExistingArray[]', key: '0xd6cbdbfc8d25c9ce4720b5fe6fa8fc536803944271617bf5425b4bd579195840', value: [], + nonDynamicName: 'NonExistingArray[]', }, ]); }); @@ -284,6 +288,7 @@ describe('Running @erc725/erc725.js tests...', () => { { name: 'LSP3Profile', key: '0x5ef83ad9559033e6e941db7d7c495acdce616347d28e90c7ce47cbfcfcad3bc5', + nonDynamicName: 'LSP3Profile', value: { verification: { method: 'keccak256(utf8)', @@ -296,6 +301,7 @@ describe('Running @erc725/erc725.js tests...', () => { name: 'LSP1UniversalReceiverDelegate', key: '0x0cfc51aec37c55a4d0b1a65c6255c4bf2fbdf6277f3cc0730c45b828b6db8b47', value: '0x36e4Eb6Ee168EF54B1E8e850ACBE51045214B313', + nonDynamicName: 'LSP1UniversalReceiverDelegate', }, ]; @@ -359,6 +365,7 @@ describe('Running @erc725/erc725.js tests...', () => { name: 'LSP1UniversalReceiverDelegate', key: '0x0cfc51aec37c55a4d0b1a65c6255c4bf2fbdf6277f3cc0730c45b828b6db8b47', value: '0x36e4Eb6Ee168EF54B1E8e850ACBE51045214B313', + nonDynamicName: 'LSP1UniversalReceiverDelegate', }); }); }); @@ -400,7 +407,9 @@ describe('Running @erc725/erc725.js tests...', () => { ]); assert.deepStrictEqual(data[0], { key: '0x4b80742de2bf82acb36300009139def55c73c12bcda9c44f12326686e3948634', - name: 'AddressPermissions:Permissions:9139def55c73c12bcda9c44f12326686e3948634', + name: 'AddressPermissions:Permissions:
', + nonDynamicName: + 'AddressPermissions:Permissions:9139def55c73c12bcda9c44f12326686e3948634', value: '0x0000000000000000000000000000000000000000000000000000000000000002', }); @@ -444,7 +453,9 @@ describe('Running @erc725/erc725.js tests...', () => { ]); assert.deepStrictEqual(data[0], { key: '0x6de85eaf5d982b4e5da000009139def55c73c12bcda9c44f12326686e3948634', - name: 'LSP4CreatorsMap:9139def55c73c12bcda9c44f12326686e3948634', + name: 'LSP4CreatorsMap:
', + nonDynamicName: + 'LSP4CreatorsMap:9139def55c73c12bcda9c44f12326686e3948634', value: ['0x24871b3d', 0], }); }); @@ -472,6 +483,7 @@ describe('Running @erc725/erc725.js tests...', () => { { name: 'LSP3Profile', key: '0x5ef83ad9559033e6e941db7d7c495acdce616347d28e90c7ce47cbfcfcad3bc5', + nonDynamicName: 'LSP3Profile', value: { verification: { method: 'keccak256(utf8)', @@ -484,6 +496,7 @@ describe('Running @erc725/erc725.js tests...', () => { name: 'LSP1UniversalReceiverDelegate', key: '0x0cfc51aec37c55a4d0b1a65c6255c4bf2fbdf6277f3cc0730c45b828b6db8b47', value: '0x36e4Eb6Ee168EF54B1E8e850ACBE51045214B313', + nonDynamicName: 'LSP1UniversalReceiverDelegate', }, ]; @@ -532,6 +545,7 @@ describe('Running @erc725/erc725.js tests...', () => { name: 'LSP3Profile', key: '0x5ef83ad9559033e6e941db7d7c495acdce616347d28e90c7ce47cbfcfcad3bc5', value: null, + nonDynamicName: 'LSP3Profile', }); }); @@ -599,16 +613,20 @@ describe('Running @erc725/erc725.js tests...', () => { { key: '0x48643a15ac5407a175674ab0f8c92df5ae90694dac72ebf0a058fb2599e3b06a', name: 'MyURL', + nonDynamicName: 'MyURL', value: 'ipfs://QmbErKh3FjsAR6YjsTjHZNm6McDp6aRt82Ftcv9AJJvZbd', }, { key: '0x74ac2555c10b9349e78f0000b74a88c43bcf691bd7a851f6603cb1868f6fc147', - name: 'LSP12IssuedAssetsMap:b74a88C43BCf691bd7A851f6603cb1868f6fc147', + name: 'LSP12IssuedAssetsMap:
', + nonDynamicName: + 'LSP12IssuedAssetsMap:b74a88C43BCf691bd7A851f6603cb1868f6fc147', value: '0x1098603B193d276f5fA176CC02007B609F9DAE6b', }, { key: '0xeafec4d89fa9619884b600005ef83ad9559033e6e941db7d7c495acdce616347', name: 'SupportedStandards:LSP3Profile', + nonDynamicName: 'SupportedStandards:LSP3Profile', value: '0x5ef83ad9', }, ]); @@ -704,6 +722,7 @@ describe('Running @erc725/erc725.js tests...', () => { key: testJSONURLSchema.key, name: testJSONURLSchema.name, value: JSON.parse(jsonString), + nonDynamicName: testJSONURLSchema.name, }); }); @@ -743,7 +762,9 @@ describe('Running @erc725/erc725.js tests...', () => { }); assert.deepStrictEqual(result, { - name: 'JSONForAddress:cafecafecafecafecafecafecafecafecafecafe', + name: 'JSONForAddress:
', + nonDynamicName: + 'JSONForAddress:cafecafecafecafecafecafecafecafecafecafe', key: '0x84b02f6e50a0a0819a4f0000cafecafecafecafecafecafecafecafecafecafe', value: JSON.parse(jsonString), }); @@ -817,7 +838,7 @@ describe('Running @erc725/erc725.js tests...', () => { const result = await erc725.getData( schemaElement.dynamicKeyParts ? { - keyName: schemaElement.key, + keyName: schemaElement.name, dynamicKeyParts: schemaElement.dynamicKeyParts, } : schemaElement.key, @@ -826,6 +847,7 @@ describe('Running @erc725/erc725.js tests...', () => { name: schemaElement.name, key: schemaElement.key, value: schemaElement.expectedResult, + nonDynamicName: schemaElement.nonDynamicName, }); }); @@ -838,7 +860,7 @@ describe('Running @erc725/erc725.js tests...', () => { const result = await erc725.getData( schemaElement.dynamicKeyParts ? { - keyName: schemaElement.key, + keyName: schemaElement.name, dynamicKeyParts: schemaElement.dynamicKeyParts, } : schemaElement.key, @@ -847,6 +869,7 @@ describe('Running @erc725/erc725.js tests...', () => { name: schemaElement.name, key: schemaElement.key, value: schemaElement.expectedResult, + nonDynamicName: schemaElement.nonDynamicName, }); }); }); @@ -1136,6 +1159,7 @@ describe('Running @erc725/erc725.js tests...', () => { const result = erc725.encodeData([ { keyName: schemaElement.name, + dynamicKeyParts: schemaElement.dynamicKeyParts, value: schemaElement.expectedResult, }, ]); @@ -1979,7 +2003,7 @@ describe('decodeMappingKey', () => { [ { type: 'bytes16', - value: '00000000000000000000000012345678', + value: '0x12345678000000000000000000000000', }, ], ); diff --git a/src/lib/decodeData.test.ts b/src/lib/decodeData.test.ts index 662e5e16..5d102942 100644 --- a/src/lib/decodeData.test.ts +++ b/src/lib/decodeData.test.ts @@ -43,7 +43,7 @@ describe('decodeData', () => { valueContent: 'JSONURL', // Deprecated - We keep it for backward compatibility between v0.21.3 and v0.22.0 }, { - name: 'MyKeyName2::', + name: 'MyKeyName2::', key: '0x', keyType: 'MappingWithGrouping', valueType: 'bytes', @@ -309,9 +309,9 @@ describe('decodeData', () => { const decodedData = decodeData( [ { - keyName: 'MyKeyName2::', + keyName: 'MyKeyName2::', dynamicKeyParts: [ - '0xaaaabbbbccccddddeeeeffff111122223333444455556666777788889999aaaa', + '0xaaaabbbbccccddddeeeeffff1111222233334444', 'true', ], value: @@ -331,8 +331,8 @@ describe('decodeData', () => { schemas, ); - expect(decodedData.map(({ name }) => name)).to.eql([ - 'MyKeyName2:aaaabbbbccccddddeeeeffff111122223333444455556666777788889999aaaa:true', + expect(decodedData.map(({ nonDynamicName }) => nonDynamicName)).to.eql([ + 'MyKeyName2:aaaabbbbccccddddeeeeffff1111222233334444:true', 'MyDynamicKey2:cafecafecafecafecafecafecafecafecafecafe', 'KeyTwo', ]); diff --git a/src/lib/decodeData.ts b/src/lib/decodeData.ts index a6b7cefd..5a54e692 100644 --- a/src/lib/decodeData.ts +++ b/src/lib/decodeData.ts @@ -329,6 +329,9 @@ export function decodeData( const schemaElement: ERC725JSONSchema = isDynamic ? getSchemaElement(schema, keyName, dynamicKeyParts) : getSchemaElement(schema, keyName); + // if (isDynamic && !dynamicKeyParts) { + // decodeKey + // } let decodedValue: any = null; try { decodedValue = decodeKey(schemaElement, value); @@ -338,9 +341,12 @@ export function decodeData( } console.error(error); } + const { key, name, nonDynamicName } = schemaElement; return { - key: schemaElement.key, - name: schemaElement.name, + key, + name, + ...(nonDynamicName ? { nonDynamicName } : { nonDynamicName: name }), + ...(dynamicKeyParts ? { dynamicKeyParts } : {}), value: decodedValue, }; }; diff --git a/src/lib/decodeMappingKey.test.ts b/src/lib/decodeMappingKey.test.ts index 3f804b90..ee24598d 100644 --- a/src/lib/decodeMappingKey.test.ts +++ b/src/lib/decodeMappingKey.test.ts @@ -44,9 +44,9 @@ describe('decodeDynamicKeyParts', () => { key: { name: 'MyKeyName:', encoded: - '0x35e6950bc8d21a1699e5000000000000000000000000000000000000abcd1234', + '0x35e6950bc8d21a1699e50000abcd123400000000000000000000000000000000', }, - dynamicKeyParts: [{ type: 'bytes4', value: 'abcd1234' }], + dynamicKeyParts: [{ type: 'bytes4', value: '0xabcd1234' }], }, { key: { @@ -57,7 +57,8 @@ describe('decodeDynamicKeyParts', () => { dynamicKeyParts: [ { type: 'bytes32', - value: 'aaaabbbbccccddddeeeeffff1111222233334444', + value: + '0xaaaabbbbccccddddeeeeffff1111222233334444000000000000000000000000', }, ], }, @@ -94,23 +95,23 @@ describe('decodeDynamicKeyParts', () => { key: { name: 'MyKeyName::', encoded: - '0x35e6950bc8d20000ffff000000000000000000000000000000000000f342d33d', + '0x35e6950bc8d2ffff0000000000000000000000000000000000000000f342d33d', }, dynamicKeyParts: [ - { type: 'bytes2', value: 'ffff' }, + { type: 'bytes2', value: '0xffff' }, { type: 'uint32', value: 4081242941 }, ], }, { key: { - name: 'MyKeyName:
:
', + name: 'MyKeyName::
', encoded: '0x35e6950bc8d2abcdef110000cafecafecafecafecafecafecafecafecafecafe', }, dynamicKeyParts: [ { - type: 'address', - value: '0x00000000000000000000000000000000AbCDeF11', + type: 'bytes4', + value: '0xabcdef11', }, { type: 'address', @@ -120,25 +121,28 @@ describe('decodeDynamicKeyParts', () => { }, { key: { - name: 'MyKeyName:MyMapName:', + name: 'MyKeyName:MyMapName:', encoded: '0x35e6950bc8d275060e3c0000aaaabbbbccccddddeeeeffff1111222233334444', }, dynamicKeyParts: [ { - type: 'bytes32', - value: 'aaaabbbbccccddddeeeeffff1111222233334444', + type: 'bytes16', + value: '0xaaaabbbbccccddddeeeeffff11112222', }, ], }, { key: { - name: 'MyKeyName::', + name: 'MyKeyName::', encoded: '0x35e6950bc8d2aaaabbbb00000000000000000000000000000000000000000001', }, dynamicKeyParts: [ - { type: 'bytes32', value: 'aaaabbbb' }, + { + type: 'bytes19', + value: '0xaaaabbbb000000000000000000000000000000', + }, { type: 'bool', value: true }, ], }, diff --git a/src/lib/decodeMappingKey.ts b/src/lib/decodeMappingKey.ts index 7e3d40ac..f8417e60 100644 --- a/src/lib/decodeMappingKey.ts +++ b/src/lib/decodeMappingKey.ts @@ -18,11 +18,12 @@ * @date 2022 */ -import { padLeft } from 'web3-utils'; +import { padLeft, padRight } from 'web3-utils'; import { isHex } from 'web3-validator'; import { decodeValueType } from './encoder'; import { ERC725JSONSchema } from '../types/ERC725JSONSchema'; import { DynamicKeyPart } from '../types/dynamicKeys'; +import { dynamicKeySize } from './encodeKeyName'; function isDynamicKeyPart(keyPartName: string): boolean { return ( @@ -45,18 +46,16 @@ function decodeKeyPart( let decodedKey: string | number | boolean | undefined; const type = keyPartName.slice(1, keyPartName.length - 1); - - if (type === 'bool') + if (type === 'bool') { decodedKey = encodedKeyPart.slice(encodedKeyPart.length - 1) === '1'; - else if (type.includes('uint')) + } else if (type.includes('uint')) decodedKey = Number.parseInt(encodedKeyPart, 16); else if (type.includes('bytes')) { - const bytesLength = Number.parseInt(type.replace('bytes', ''), 10) * 2; - const sliceFrom = - encodedKeyPart.length - bytesLength < 0 - ? 0 - : encodedKeyPart.length - bytesLength; - decodedKey = encodedKeyPart.slice(sliceFrom); + const charLength = Number.parseInt(type.replace('bytes', ''), 10) * 2; + decodedKey = padRight( + `0x${encodedKeyPart.slice(0, charLength)}`, + charLength, + ); } else if (type === 'address') { // this is required if the 2nd word is an address in a MappingWithGrouping const leftPaddedAddress = padLeft(`0x${encodedKeyPart}`, 40); @@ -108,10 +107,19 @@ export function decodeMappingKey( dynamicParts.push(decodeKeyPart(hashedKey.slice(26), keyParts[1])); break; - case 3: // MappingWithGrouping - dynamicParts.push(decodeKeyPart(hashedKey.slice(14, 22), keyParts[1])); - dynamicParts.push(decodeKeyPart(hashedKey.slice(26), keyParts[2])); + case 3: { + // MappingWithGrouping + const len = dynamicKeySize(keyParts[1]); + if (keyParts[1].startsWith('<')) { + dynamicParts.push( + decodeKeyPart(hashedKey.slice(14, 14 + len * 2), keyParts[1]), + ); + } + dynamicParts.push( + decodeKeyPart(hashedKey.slice(14 + len * 2 + 4), keyParts[2]), + ); break; + } default: break; } diff --git a/src/lib/encodeKeyName.test.ts b/src/lib/encodeKeyName.test.ts index 05dfa044..fc2566ce 100644 --- a/src/lib/encodeKeyName.test.ts +++ b/src/lib/encodeKeyName.test.ts @@ -48,7 +48,7 @@ describe('encodeKeyName', () => { '0xeafec4d89fa9619884b600005ef83ad9559033e6e941db7d7c495acdce616347', }, { - keyName: 'MyCoolAddress:0xcafecafecafecafecafecafecafecafecafecafe', + keyName: 'MyCoolAddress:cafecafecafecafecafecafecafecafecafecafe', expectedKey: '0x22496f48a493035f0ab40000cafecafecafecafecafecafecafecafecafecafe', }, @@ -119,17 +119,16 @@ describe('encodeKeyName', () => { { keyName: 'MyKeyName:', expectedKey: - '0x35e6950bc8d21a1699e5000000000000000000000000000000000000abcd1234', + '0x35e6950bc8d21a1699e50000abcd123400000000000000000000000000000000', // |--keccak256 bytes12-||--||---bytes20: keccak256()---------------| dynamicKeyParts: '0xabcd1234', }, { - keyName: 'MyKeyName:', + keyName: 'MyKeyName:', expectedKey: '0x35e6950bc8d21a1699e50000aaaabbbbccccddddeeeeffff1111222233334444', // |--keccak256 bytes12-||--||---bytes20: keccak256()---------------| - dynamicKeyParts: - '0xaaaabbbbccccddddeeeeffff111122223333444455556666777788889999aaaa', + dynamicKeyParts: '0xaaaabbbbccccddddeeeeffff1111222233334444', }, { keyName: 'MyKeyName:', @@ -163,54 +162,47 @@ describe('encodeKeyName', () => { { keyName: 'MyKeyName::', expectedKey: - '0x35e6950bc8d20000ffff000000000000000000000000000000000000f342d33d', + '0x35e6950bc8d2ffff0000000000000000000000000000000000000000f342d33d', // |-- bytes6 --||------||--||------------ bytes20 -----------------| dynamicKeyParts: ['ffff', '4081242941'], }, { keyName: 'MyKeyName::', expectedKey: - '0x35e6950bc8d20000ffff000000000000000000000000000000000000f342d33d', + '0x35e6950bc8d2ffff0000000000000000000000000000000000000000f342d33d', // |-- bytes6 --||------||--||------------ bytes20 -----------------| dynamicKeyParts: ['ffff', '0xf342d33d'], }, { - keyName: 'MyKeyName:
:
', + keyName: 'MyKeyName::
', expectedKey: '0x35e6950bc8d2abcdef110000cafecafecafecafecafecafecafecafecafecafe', // |-- bytes6 --||------||--||------------ bytes20 -----------------| dynamicKeyParts: [ - '0xabcdef11abcdef11abcdef11abcdef11ffffffff', + '0xabcdef11', '0xcafecafecafecafecafecafecafecafecafecafe', ], }, { - keyName: 'MyKeyName:MyMapName:', + keyName: 'MyKeyName:MyMapName:', expectedKey: '0x35e6950bc8d275060e3c0000aaaabbbbccccddddeeeeffff1111222233334444', // |-- bytes6 --||------||--||------------ bytes20 -----------------| - dynamicKeyParts: [ - '0xaaaabbbbccccddddeeeeffff111122223333444455556666777788889999aaaa', - ], + dynamicKeyParts: ['0xaaaabbbbccccddddeeeeffff1111222233334444'], }, { - keyName: 'MyKeyName::', + keyName: 'MyKeyName::', expectedKey: - '0x35e6950bc8d2aaaabbbb00000000000000000000000000000000000000000001', + '0x35e6950bc8d2aaaabbbbccccddddeeeeffff1111222233334400000000000001', // |-- bytes6 --||------||--||------------ bytes20 -----------------| - dynamicKeyParts: [ - '0xaaaabbbbccccddddeeeeffff111122223333444455556666777788889999aaaa', - 'true', - ], + dynamicKeyParts: ['0xaaaabbbbccccddddeeeeffff11112222333344', 'true'], }, { - keyName: 'MyKeyName::MyMapName', + keyName: 'MyKeyName::MyMapName', expectedKey: - '0x35e6950bc8d2aaaabbbb000075060e3cd7d40450e94d415fb5992ced9ad8f058', + '0x35e6950bc8d2aaaabbbbccccddddeeeeffff11112222000075060e3cd7d40450', // |-- bytes6 --||------||--||------------ bytes20 -----------------| - dynamicKeyParts: [ - '0xaaaabbbbccccddddeeeeffff111122223333444455556666777788889999aaaa', - ], + dynamicKeyParts: ['0xaaaabbbbccccddddeeeeffff11112222'], }, ]; @@ -335,8 +327,8 @@ describe('encodeDynamicKeyPart', () => { { type: '
', value: '7f268357a8c2552623316e2562d90e642bb538e5', - bytes: 21, - expectedEncoding: '007f268357a8c2552623316e2562d90e642bb538e5', // left padded + bytes: 20, + expectedEncoding: '7f268357a8c2552623316e2562d90e642bb538e5', // left padded }, { type: '', @@ -367,19 +359,19 @@ describe('encodeDynamicKeyPart', () => { type: '', value: '0xd1b2917d26eeeaad', bytes: 12, - expectedEncoding: '00000000d1b2917d26eeeaad', // left padded + expectedEncoding: 'd1b2917d26eeeaad00000000', // right padded/cut }, { type: '', value: 'd1b2917d26eeeaad', // test without 0x prefix bytes: 12, - expectedEncoding: '00000000d1b2917d26eeeaad', // left padded + expectedEncoding: 'd1b2917d26eeeaad00000000', // right padded/cut }, { type: '', value: 'd1b2917d26eeeaad', bytes: 4, - expectedEncoding: 'd1b2917d', // right cut + expectedEncoding: 'd1b2917d', // right padded/cut }, ]; diff --git a/src/lib/encodeKeyName.ts b/src/lib/encodeKeyName.ts index 8c3c76a4..7503ddd5 100644 --- a/src/lib/encodeKeyName.ts +++ b/src/lib/encodeKeyName.ts @@ -31,6 +31,56 @@ const dynamicTypes = ['', '
', '']; // https://docs.soliditylang.org/en/v0.8.14/abi-spec.html#types export const dynamicTypesRegex = /<(uint|int|bytes)(\d+)>/; +export const dynamicKeySize = (type: string) => { + if (!isDynamicKeyName(type)) { + return 4; + } + let baseType = ''; + let size = 0; + + if (dynamicTypes.includes(type)) { + baseType = type.slice(1, -1); + } else { + const regexMatch = type.match(dynamicTypesRegex); + + if (!regexMatch) { + throw new Error(`Dynamic key type: ${type} is not supported`); + } + // eslint-disable-next-line prefer-destructuring + baseType = regexMatch[1]; + size = Number.parseInt(regexMatch[2], 10); + } + + switch (baseType) { + case 'string': + return 4; + case 'bool': { + return 1; + } + case 'address': { + return 20; + } + case 'uint': { + if (size > 256 || size % 8 !== 0) { + throw new Error( + `Wrong dynamic key type: ${type}. 0 < M <= 256, M % 8 == 0. Got: ${size}.`, + ); + } + + // NOTE: we could verify if the number given is not too big for the given size. + // e.g.: uint8 max value is 255, uint16 is 65535... + return Math.floor(size / 8); + } + case 'int': + // TODO: + throw new Error('The encoding of has not been implemented yet.'); + case 'bytes': { + return size; + } + default: + throw new Error(`Dynamic key: ${type} is not supported`); + } +}; /** * * @param type , , , , ,
. @@ -68,7 +118,7 @@ export const encodeDynamicKeyPart = ( `Wrong value: ${value} for dynamic key with type: . Expected "true" or "false".`, ); } - return leftPad(+(value === 'true'), bytes * 2).slice(2); + return leftPad(value === 'true' ? 1 : 0, bytes * 2).slice(2); } case 'address': { if (!isAddress(value)) { @@ -77,8 +127,12 @@ export const encodeDynamicKeyPart = ( ); } - if (bytes > 20) { - return leftPad(value.replace('0x', ''), bytes * 2).toLowerCase(); + if (value.length < 22) { + let c = value.replace('0x', '').toLowerCase(); + while (c.length < 40) { + c = `0${c}`; + } + return c; } return value @@ -89,7 +143,7 @@ export const encodeDynamicKeyPart = ( case 'uint': { if (size > 256 || size % 8 !== 0) { throw new Error( - `Wrong dynamic key type: ${type}. 0 < M <= 256, M % 8 == 0. Got: ${size}.`, + `Wrong dynamic key type: $type. 0 < M <= 256, M % 8 == 0. Got: ${size}.`, ); } @@ -109,7 +163,7 @@ export const encodeDynamicKeyPart = ( case 'bytes': { if (!isHex(value)) { throw new Error( - `Wrong value: ${value} for dynamic key with type: ${type}. Value is not in hex.`, + `Wrong value: ${value} for dynamic key with type: $type. Value is not in hex.`, ); } const valueWithoutPrefix = stripHexPrefix(value); @@ -123,7 +177,11 @@ export const encodeDynamicKeyPart = ( return valueWithoutPrefix.slice(0, bytes * 2); // right cut } - return stripHexPrefix(leftPad(value, bytes * 2).toLowerCase()); + let result = stripHexPrefix(value).toLowerCase(); + while (result.length < bytes * 2) { + result = `${result}0`; + } + return result; } default: throw new Error(`Dynamic key: ${type} is not supported`); @@ -132,6 +190,9 @@ export const encodeDynamicKeyPart = ( // This function does not support multi dynamic types such as MyName: export function isDynamicKeyName(name: string) { + if (name.startsWith('0x') && name.includes('<')) { + return true; + } const keyNameParts = name.split(':'); for (let i = 0; i < keyNameParts.length; i++) { @@ -205,21 +266,39 @@ const encodeDynamicMappingWithGrouping = ( const firstPart = keccak256(keyNameSplit[0]).slice(0, 14); let secondPart = ''; + const firstLength = dynamicKeySize(keyNameSplit[1]); + if (firstLength > 20) { + throw new Error( + `Can't encode dynamic key of type: MappingWithGrouping. The first part is too long: ${keyNameSplit.join(':')}`, + ); + } + const secondLength = dynamicKeySize(keyNameSplit[2]); if (isDynamicKeyName(keyNameSplit[1])) { - secondPart = encodeDynamicKeyPart(keyNameSplit[1], dynamicKeyParts[0], 4); + secondPart = encodeDynamicKeyPart( + keyNameSplit[1], + dynamicKeyParts[0], + firstLength, + ); } else { secondPart = keccak256(keyNameSplit[1]).slice(2, 2 + 4 * 2); } - let lastPart = ''; if (isDynamicKeyName(keyNameSplit[2])) { + if (firstLength + secondLength > 24) { + throw new Error( + `Can't encode dynamic key of type: MappingWithGrouping. The first part is too long: ${keyNameSplit.join(':')}`, + ); + } lastPart = encodeDynamicKeyPart( keyNameSplit[2], dynamicKeyParts[dynamicKeyParts.length - 1], - 20, + 20 - (firstLength - 4), ); } else { - lastPart = keccak256(keyNameSplit[2]).slice(2, 2 + 20 * 2); + lastPart = keccak256(keyNameSplit[2]).slice( + 2, + 2 + (20 - (firstLength - 4)) * 2, + ); } return `${firstPart}${secondPart}0000${lastPart}`; @@ -345,3 +424,5 @@ export const generateDynamicKeyName = ( }) .join(':'); }; + +encodeKeyName('MyKeyName:', ['0x12345678']); diff --git a/src/lib/encoder.ts b/src/lib/encoder.ts index df0a7ce3..7098c960 100644 --- a/src/lib/encoder.ts +++ b/src/lib/encoder.ts @@ -979,12 +979,7 @@ export function decodeValueContent( value: string, ): string | URLDataWithHash | number | boolean | null { if (isValueContentLiteralHex(valueContent)) { - if (valueContent.toLowerCase() !== value) { - throw new Error( - `Could not decode value content: the value ${value} does not match the Hex Literal ${valueContent} defined in the \`valueContent\` part of the schema`, - ); - } - return valueContent; + return value; } if (value == null || value === '0x') { diff --git a/src/lib/getSchemaElement.test.ts b/src/lib/getSchemaElement.test.ts index 0469391c..53fedb26 100644 --- a/src/lib/getSchemaElement.test.ts +++ b/src/lib/getSchemaElement.test.ts @@ -68,11 +68,14 @@ describe('getSchemaElement', () => { ['0x2ab3903c6e5815f4bc2a95b7f3b22b6a289bacac'], ), { - name: 'LSP12IssuedAssetsMap:2ab3903c6e5815f4bc2a95b7f3b22b6a289bacac', + nonDynamicName: + 'LSP12IssuedAssetsMap:2ab3903c6e5815f4bc2a95b7f3b22b6a289bacac', + name: 'LSP12IssuedAssetsMap:
', key: '0x74ac2555c10b9349e78f00002ab3903c6e5815f4bc2a95b7f3b22b6a289bacac', keyType: 'Mapping', valueType: '(bytes4,uint128)', valueContent: '(Bytes4,Number)', + dynamicKeyParts: ['0x2ab3903c6e5815f4bc2a95b7f3b22b6a289bacac'], }, ); }); diff --git a/src/lib/getSchemaElement.ts b/src/lib/getSchemaElement.ts index 81d3b9fb..496cac56 100644 --- a/src/lib/getSchemaElement.ts +++ b/src/lib/getSchemaElement.ts @@ -25,6 +25,7 @@ import { generateDynamicKeyName, isDynamicKeyName, } from './encodeKeyName'; +import { decodeMappingKey } from './decodeMappingKey'; /** * @@ -51,12 +52,16 @@ const getSchemaElementForDynamicKeyName = ( // once we have the schemaElement with dynamic parts, we need to replace the name and the key: const key = encodeKeyName(namedDynamicKey, dynamicKeyParts); - const name = generateDynamicKeyName(namedDynamicKey, dynamicKeyParts); + const nonDynamicName = generateDynamicKeyName( + namedDynamicKey, + dynamicKeyParts, + ); return { ...schemaElement, + nonDynamicName, key, - name, + dynamicKeyParts, }; }; @@ -74,7 +79,25 @@ export function getSchemaElement( dynamicKeyParts?: DynamicKeyParts, ): ERC725JSONSchema { let keyHash: string; - + if (namedOrHashedKey.startsWith('0x')) { + const index = namedOrHashedKey.indexOf('<'); + if (index !== -1) { + const partial = namedOrHashedKey.slice(0, index); + const schemaElement = schemas.find( + (e) => e.key.slice(0, index) === partial, + ); + const dynamicKeyParts = decodeMappingKey( + namedOrHashedKey, + schemaElement as ERC725JSONSchema, + ) as unknown as DynamicKeyParts; + if (schemaElement) { + return { + ...schemaElement, + ...(dynamicKeyParts ? { dynamicKeyParts } : {}), + }; + } + } + } if (isDynamicKeyName(namedOrHashedKey)) { if (!dynamicKeyParts) { throw new Error( diff --git a/src/lib/schemaParser.test.ts b/src/lib/schemaParser.test.ts index dcd8d60f..f10f188a 100644 --- a/src/lib/schemaParser.test.ts +++ b/src/lib/schemaParser.test.ts @@ -106,7 +106,7 @@ describe('schemaParser getSchema', () => { ); assert.deepStrictEqual(schema, { - name: 'SupportedStandards:??????', + name: 'SupportedStandards:LSP3Profile', key: '0xeafec4d89fa9619884b60000f4d7faed14a1ab658d46d385bc29fb1eeaa56d0b', keyType: 'Mapping', valueContent: '0x5ef83ad9', diff --git a/src/lib/schemaParser.ts b/src/lib/schemaParser.ts index 3cdde5ec..dc34cdf7 100644 --- a/src/lib/schemaParser.ts +++ b/src/lib/schemaParser.ts @@ -87,7 +87,8 @@ const fillDynamicKeyPart = ( if (keccak256(keyNameParts[1]).substring(0, 42) === `0x${secondWordHex}`) { dynamicPartName = keyNameParts[1]; } - result.name = `${keyNameParts[0]}:${dynamicPartName}`; + + // result.name = `${keyNameParts[0]}:${dynamicPartName}`; return result; }; diff --git a/src/lib/utils.test.ts b/src/lib/utils.test.ts index 5c07e0bc..471447f2 100644 --- a/src/lib/utils.test.ts +++ b/src/lib/utils.test.ts @@ -376,8 +376,8 @@ describe('utils', () => { encodedValue: '0x74657374', }, { - valueContent: '0xc9aaAE3201F40fd0fF04D9c885769d8256A456ab', - valueType: 'bytes', + valueContent: 'Address', + valueType: 'address', decodedValue: '0xc9aaAE3201F40fd0fF04D9c885769d8256A456ab', encodedValue: '0xc9aaae3201f40fd0ff04d9c885769d8256a456ab', // encoded hex is always lower case }, @@ -930,7 +930,7 @@ describe('utils', () => { }, { keyType: 'Mapping', - keyName: 'MyCoolAddress:0xcafecafecafecafecafecafecafecafecafecafe', + keyName: 'MyCoolAddress:cafecafecafecafecafecafecafecafecafecafe', }, { keyType: 'Mapping', @@ -944,7 +944,7 @@ describe('utils', () => { { keyType: 'MappingWithGrouping', keyName: - 'AddressPermissions:Permissions:0xcafecafecafecafecafecafecafecafecafecafe', + 'AddressPermissions:Permissions:cafecafecafecafecafecafecafecafecafecafe', }, ]; @@ -1027,7 +1027,7 @@ describe('utils', () => { generatedSchemas.forEach((schema) => { expect( - isDynamicKeyName(schema.name), + isDynamicKeyName(schema.key), 'generated schema key should not be dynamic', ).to.be.false; }); diff --git a/src/lib/utils.ts b/src/lib/utils.ts index cb6ccc20..eba20a82 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -177,7 +177,7 @@ export function guessKeyTypeFromKeyName( // This function could not work with subsequents keys of an Array // It will always assume the given key, if array, is the initial array key. - const splittedKeyName = keyName.split(':'); + const splittedKeyName = keyName.replace(/[^:] } | Record; + dynamicKeyParts?: DynamicKeyParts; + nonDynamicName?: string; name: string; key: string; } diff --git a/test/mockSchema.ts b/test/mockSchema.ts index 295d8ddc..b0e2b9f8 100644 --- a/test/mockSchema.ts +++ b/test/mockSchema.ts @@ -16,6 +16,7 @@ export const mockSchema: (ERC725JSONSchema & { // Case 1 { name: 'SupportedStandards:LSP3Profile', + nonDynamicName: 'SupportedStandards:LSP3Profile', key: '0xeafec4d89fa9619884b600005ef83ad9559033e6e941db7d7c495acdce616347', keyType: 'Mapping', valueContent: '0x5ef83ad9', @@ -30,6 +31,7 @@ export const mockSchema: (ERC725JSONSchema & { // Case 2 { name: 'TestJSONURL', + nonDynamicName: 'TestJSONURL', key: '0xd154e1e44d32870ff5ade9e8726fd06d0ed6c996f5946dabfdfd46aa6dd2ea99', keyType: 'Singleton', valueContent: 'JSONURL', @@ -56,6 +58,7 @@ export const mockSchema: (ERC725JSONSchema & { // Case 3 { name: 'TestAssetURL', + nonDynamicName: 'TestAssetURL', key: '0xf18290c9b373d751e12c5ec807278267a807c35c3806255168bc48a85757ceee', keyType: 'Singleton', valueContent: 'AssetURL', @@ -82,6 +85,7 @@ export const mockSchema: (ERC725JSONSchema & { // Case 4 { name: 'TestKeccak256', + nonDynamicName: 'TestKeccak256', key: '0xd6c7198ea09a1d3357688e1dbdf0e07f6cfaf94359e0a4fc11e4f5f1d59d54f4', keyType: 'Singleton', valueContent: 'Keccak256', @@ -103,6 +107,7 @@ export const mockSchema: (ERC725JSONSchema & { // Case 5 { name: 'TestAddress', + nonDynamicName: 'TestAddress', key: '0x7bf6ecfbf659a88c662d7f099c14e468610f786f6e29f0d346e44f772ef0d187', keyType: 'Singleton', valueContent: 'Address', @@ -122,6 +127,7 @@ export const mockSchema: (ERC725JSONSchema & { // Case 6 { name: 'TestMarkdown', + nonDynamicName: 'TestMarkdown', key: '0x328f991bde3a9d8c548b7b2dbc303a362202dddbcd33219650d85bedcd75ac9b', keyType: 'Singleton', valueContent: 'Markdown', @@ -143,6 +149,7 @@ export const mockSchema: (ERC725JSONSchema & { // Case 7 { name: 'LSP3Name', + nonDynamicName: 'LSP3Name', key: '0xa5f15b1fa920bbdbc28f5d785e5224e3a66eb5f7d4092dc9ba82d5e5ae3abc87', keyType: 'Singleton', valueContent: 'String', @@ -157,6 +164,7 @@ export const mockSchema: (ERC725JSONSchema & { // Case 8 { name: 'LSP3Profile', + nonDynamicName: 'LSP3Profile', key: '0x5ef83ad9559033e6e941db7d7c495acdce616347d28e90c7ce47cbfcfcad3bc5', keyType: 'Singleton', valueContent: 'URL', @@ -178,6 +186,7 @@ export const mockSchema: (ERC725JSONSchema & { // Case 9 { name: 'LSP12IssuedAssets[]', + nonDynamicName: 'LSP12IssuedAssets[]', key: '0x7c8c3416d6cda87cd42c71ea1843df28ac4850354f988d55ee2eaa47b6dc05cd', keyType: 'Array', valueContent: 'Address', @@ -211,6 +220,7 @@ export const mockSchema: (ERC725JSONSchema & { { name: 'LSP3IssuedAssetsWithEmptyValue[]', + nonDynamicName: 'LSP3IssuedAssetsWithEmptyValue[]', key: '0xbcdf8aea8f803343f50b03205ac25188e17fc1f5e4e42245b0782f68786d9f92', keyType: 'Array', valueContent: 'Address', @@ -240,6 +250,7 @@ export const mockSchema: (ERC725JSONSchema & { // // Case 10 { name: 'TestObjArray[]', + nonDynamicName: 'TestObjArray[]', key: '0x9985edaf12cbacf5ac7d6ed54f0445cc0ea56075aee9b9942e4ab3bf4239f950', keyType: 'Array', valueContent: 'JSONURL', @@ -296,6 +307,7 @@ export const mockSchema: (ERC725JSONSchema & { // Case 11 { name: 'TestStringValueType', + nonDynamicName: 'TestStringValueType', key: '0xc0929170bbaeb216f869c80a5c937f7a1c887a5a92262dac50313aef131f0c03', keyType: 'Singleton', valueContent: 'String', @@ -310,6 +322,7 @@ export const mockSchema: (ERC725JSONSchema & { // // Case 12 { name: 'TestUintValueType', + nonDynamicName: 'TestUintValueType', key: '0x61529294800f5739edc21a6cf8ba1bad3fd3e11d03d2ab5219ce9c0131b93f93', keyType: 'Singleton', valueContent: 'Number', @@ -330,6 +343,7 @@ export const mockSchema: (ERC725JSONSchema & { // Case 13 { name: 'TestNumberWithBytesValueType', + nonDynamicName: 'TestNumberWithBytesValueType', key: '0x64a44e72c25d95851b1d449428d8d27093b2ef3e0b36a2b3497ae17edf979e61', keyType: 'Singleton', valueContent: 'Number', @@ -349,6 +363,7 @@ export const mockSchema: (ERC725JSONSchema & { // Case 14 { name: 'TestStringWithBytesValueType', + nonDynamicName: 'TestStringWithBytesValueType', key: '0x3ef4d417afa66557c9e1463723b391a518eee0c61d29be4e10882999c7848041', keyType: 'Singleton', valueContent: 'String', @@ -369,6 +384,7 @@ export const mockSchema: (ERC725JSONSchema & { // Case 15 { name: 'TestStringValueTypeArray', + nonDynamicName: 'TestStringValueTypeArray', key: '0xd7a8f1af4a0d9de8d17c177ff06f1689c0c3f1310edbbe53733da0b084ccff18', keyType: 'Singleton', valueContent: 'String', @@ -397,6 +413,7 @@ export const mockSchema: (ERC725JSONSchema & { // Case 16 { name: 'TestBytesValueTypeArray', + nonDynamicName: 'TestBytesValueTypeArray', key: '0xd6b3622ec62ae4459c0276bd5e2e26011201fada1cbc2b33283e9c20495c05fe', keyType: 'Singleton', valueContent: 'String', @@ -425,6 +442,7 @@ export const mockSchema: (ERC725JSONSchema & { // Case 17 { name: 'TestAddressValueTypeArray', + nonDynamicName: 'TestAddressValueTypeArray', key: '0xe45f3de809830d5ac3aeab862200fc670391fcb99018dcd2522fee7cf07f93ee', keyType: 'Singleton', valueContent: 'Address', @@ -456,6 +474,7 @@ export const mockSchema: (ERC725JSONSchema & { // // Case 18 { name: 'TestUintValueTypeArray', + nonDynamicName: 'TestUintValueTypeArray', key: '0xdaa41a5e1acc41087359e61588e80bf0b7f1d96063b98bdff73b4ce3a645b40b', keyType: 'Singleton', valueContent: 'Number', @@ -478,6 +497,7 @@ export const mockSchema: (ERC725JSONSchema & { // Case 19 { name: 'TestBytes32ValueTypeArray', + nonDynamicName: 'TestBytes32ValueTypeArray', key: '0x7e2458b2b22ff4357510c3491b7c041df2ee4f11ba4d6f4f4e34101fc2645a97', keyType: 'Singleton', valueContent: 'Keccak256', @@ -509,6 +529,7 @@ export const mockSchema: (ERC725JSONSchema & { // // Case 20 { name: 'TestURLStringValueTypeArray', + nonDynamicName: 'TestURLStringValueTypeArray', key: '0x1a9818703b62d00000bd3e8c7499296d42966619cd735a92eac7488de8881bb8', keyType: 'Singleton', valueContent: 'URL', @@ -540,6 +561,7 @@ export const mockSchema: (ERC725JSONSchema & { // Case 21 { name: 'TestHashKey', + nonDynamicName: 'TestHashKey', key: '0xed579debad05d91a79b46589987171dfce1c8ffa8b1d8c1ddc851cc104ea6029', keyType: 'Singleton', valueContent: @@ -560,8 +582,10 @@ export const mockSchema: (ERC725JSONSchema & { }, { - name: 'MyCoolAddress:0xcafecafecafecafecafecafecafecafecafecafe', + name: 'MyCoolAddress:
', + nonDynamicName: 'MyCoolAddress:cafecafecafecafecafecafecafecafecafecafe', key: '0x22496f48a493035f0ab40000cafecafecafecafecafecafecafecafecafecafe', + dynamicKeyParts: '0xcafecafecafecafecafecafecafecafecafecafe', keyType: 'Mapping', valueContent: '0x5ef83ad9', valueType: 'bytes', @@ -572,8 +596,11 @@ export const mockSchema: (ERC725JSONSchema & { expectedResult: '0x5ef83ad9', }, { - name: 'AddressPermissions:Permissions:cafecafecafecafecafecafecafecafecafecafe', + name: 'AddressPermissions:Permissions:
', + nonDynamicName: + 'AddressPermissions:Permissions:cafecafecafecafecafecafecafecafecafecafe', key: '0x4b80742de2bf82acb3630000cafecafecafecafecafecafecafecafecafecafe', + dynamicKeyParts: '0xcafecafecafecafecafecafecafecafecafecafe', keyType: 'MappingWithGrouping', valueContent: '0x5ef83ad9', valueType: 'bytes', @@ -585,6 +612,7 @@ export const mockSchema: (ERC725JSONSchema & { }, { name: 'Hello:
', + nonDynamicName: 'Hello:cafecafecafecafecafecafecafecafecafecafe', key: '0x06b3dfaec148fb1bb2b00000cafecafecafecafecafecafecafecafecafecafe', // encoded for cafecafe... address - parameters are bellow dynamicKeyParts: ['0xcafecafecafecafecafecafecafecafecafecafe'], keyType: 'Singleton', @@ -609,6 +637,7 @@ export const mockSchema: (ERC725JSONSchema & { }, { name: 'TestStringWithBytes4ValueContent', + nonDynamicName: 'TestStringWithBytes4ValueContent', key: '0xb61b0a1d86687ef022781d2698d5e0221997458e3a720cded0b8f165a029d3c5', keyType: 'Singleton', valueContent: 'Bytes4', @@ -621,6 +650,7 @@ export const mockSchema: (ERC725JSONSchema & { }, { name: 'TestStringWithBytes32ValueType', + nonDynamicName: 'TestStringWithBytes32ValueType', key: '0xbaced8d1d0b02d5f412674cac7ad60f0f3e8ae29f2b8d4ad463fa1f5fc103d4d', keyType: 'Singleton', valueContent: 'Bytes32', @@ -640,6 +670,7 @@ export const mockSchema: (ERC725JSONSchema & { }, { name: 'TestStringWithBytes4ValueType', + nonDynamicName: 'TestStringWithBytes4ValueType', key: '0x1b92e269c7ce7fc16e625562aa588403fe603edb4e2740b0558ed44faa3c1728', keyType: 'Singleton', valueContent: 'Bytes4', diff --git a/test/testHelpers.ts b/test/testHelpers.ts index b1547472..94fd59a7 100644 --- a/test/testHelpers.ts +++ b/test/testHelpers.ts @@ -17,6 +17,7 @@ * @date 2020 */ +import { encodeKeyName, isDynamicKeyName } from '../src'; import { encodeArrayKey } from '../src/lib/utils'; /** @@ -49,7 +50,9 @@ export function generateAllRawData(schema, isArrayMode: boolean) { }); } else { results.push({ - key: element.key, + key: isDynamicKeyName(element.key) + ? encodeKeyName(element.name, element.dynamicKeyParts) + : element.key, value: isArrayMode ? element.returnRawDataArray : element.returnRawData, }); } @@ -108,6 +111,7 @@ export function generateAllResults(schemas) { name: schema.name, key: schema.key, value: schema.expectedResult, + nonDynamicName: schema.nonDynamicName, }; }); }