From f8dc5ae9651ce953b2ff9808dfb3671126adace0 Mon Sep 17 00:00:00 2001 From: CJ42 Date: Thu, 25 Apr 2024 10:43:29 +0100 Subject: [PATCH 1/5] fix: encode `uintN` with correct padding and bytes length --- src/index.test.ts | 54 +++++++++++++++++++++++++++++++++++++++++++ src/lib/utils.test.ts | 6 +++++ src/lib/utils.ts | 34 +++++++++++++-------------- 3 files changed, 77 insertions(+), 17 deletions(-) diff --git a/src/index.test.ts b/src/index.test.ts index 22913248..a872ac31 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -889,6 +889,60 @@ describe('Running @erc725/erc725.js tests...', () => { }); }); + describe('Testing `encodeData`', () => { + describe('for `uintN` as `Number`', () => { + [ + { valueType: 'uint8', valueToEncode: 10, expectedEncodedValue: '0x0a' }, + { + valueType: 'uint16', + valueToEncode: 10, + expectedEncodedValue: '0x000a', + }, + { + valueType: 'uint24', + valueToEncode: 10, + expectedEncodedValue: '0x00000a', + }, + { + valueType: 'uint32', + valueToEncode: 10, + expectedEncodedValue: '0x0000000a', + }, + { + valueType: 'uint128', + valueToEncode: 10, + expectedEncodedValue: '0x0000000000000000000000000000000a', + }, + { + valueType: 'uint256', + valueToEncode: 10, + expectedEncodedValue: + '0x000000000000000000000000000000000000000000000000000000000000000a', + }, + ].forEach((testCase) => { + it('should encode a valueType `uintN` as valueContent `Number` correctly with the right padding', () => { + const schema = { + name: 'ExampleUintN', + key: '0x512cddbe2654abd240fafbed308d91e82ff977301943f08ea825ba3e435bfa57', + keyType: 'Singleton', + valueType: testCase.valueType, + valueContent: 'Number', + }; + const erc725js = new ERC725([schema]); + const result = erc725js.encodeData([ + { keyName: schema.name, value: testCase.valueToEncode }, + ]); + + assert.equal(result.values[0], testCase.expectedEncodedValue); + }); + }); + }); + }); + + describe('Testing `decodeData`', () => { + // ... + }); + describe('Testing utility encoding & decoding functions', () => { const allGraphData = generateAllData(mockSchema) as any; /* **************************************** */ diff --git a/src/lib/utils.test.ts b/src/lib/utils.test.ts index 90212974..088dfe19 100644 --- a/src/lib/utils.test.ts +++ b/src/lib/utils.test.ts @@ -414,6 +414,12 @@ describe('utils', () => { encodedValue: '0xdeadbeaf0000000000000010', decodedValue: ['0xdeadbeaf', 16], }, + { + valueContent: '(Bytes4,Number)', + valueType: '(bytes4,uint128)', + encodedValue: '0xdeadbeaf00000000000000000000000000000020', + decodedValue: ['0xdeadbeaf', 32], + }, ]; // we may need to add more test cases! Address, etc. testCases.forEach((testCase) => { diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 155c0b72..c7fb78db 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -61,6 +61,10 @@ import { EncodeDataInput } from '../types/decodeData'; import { GetDataDynamicKey } from '../types/GetData'; import { isValidTuple } from './decodeData'; +function isValueContentLiteralHex(valueContent: string): boolean { + return valueContent.slice(0, 2) === '0x'; +} + /** * * @param {string} valueContent as per ERC725Schema definition @@ -84,17 +88,23 @@ export function encodeKeyValue( name?: string, ): string | false { const isSupportedValueContent = - !!valueContentMap(valueContent) || valueContent.slice(0, 2) === '0x'; + !!valueContentMap(valueContent) || isValueContentLiteralHex(valueContent); if (!isSupportedValueContent) { throw new Error( - `The valueContent '${valueContent}' - for ${name} is not supported.`, + `The valueContent '${valueContent}' for ${name} is not supported.`, ); } const isValueTypeArray = valueType.slice(valueType.length - 2) === '[]'; + if ( + (valueType.startsWith('uint') || valueType.startsWith('bytes')) && + typeof decodedValue !== 'object' + ) { + return encodeValueType(valueType, decodedValue); + } + if (!isValueTypeArray && !Array.isArray(decodedValue)) { // Straight forward encode return encodeValueContent(valueContent, decodedValue); @@ -187,8 +197,8 @@ export function guessKeyTypeFromKeyName( } export const encodeTupleKeyValue = ( - valueContent: string, // i.e. (bytes4,Number,bytes16) - valueType: string, // i.e. (bytes4,bytes8,bytes16) + valueContent: string, // i.e. (Bytes4,Number,Bytes16,Address) + valueType: string, // i.e. (bytes4,uint128,bytes16,address) decodedValues: Array, ) => { // We assume data has already been validated at this stage @@ -196,6 +206,7 @@ export const encodeTupleKeyValue = ( const valueTypeParts = valueType .substring(1, valueType.length - 1) .split(','); + const valueContentParts = valueContent .substring(1, valueContent.length - 1) .split(','); @@ -218,18 +229,7 @@ export const encodeTupleKeyValue = ( return ''; // may cause issues? } - const numberOfBytes = Number.parseInt(valueTypeParts[i].substring(5), 10); // bytes50 -> 50 - - // If the encoded value is too large for the expected valueType, we shrink it from the left - // i.e. number are encoded on 32bytes - // TODO: might be missing cases ! - if (encodedKeyValue.length > 2 + numberOfBytes * 2) { - return encodedKeyValue.slice( - encodedKeyValue.length - numberOfBytes * 2, - ); - } - - return padLeft(encodedKeyValue, numberOfBytes * 2).replace('0x', ''); + return stripHexPrefix(encodedKeyValue); }) .join('')}`; From 1db6f2faebaccdcc39455b4a3dbae05c6416271a Mon Sep 17 00:00:00 2001 From: CJ42 Date: Thu, 25 Apr 2024 11:33:44 +0100 Subject: [PATCH 2/5] feat: add support for `bytesN` valueType from 1 to 32 --- src/lib/encoder.test.ts | 51 +++++++++++++++++++--- src/lib/encoder.ts | 96 ++++++++++++++++++++++++++++++----------- src/lib/utils.ts | 10 +++++ test/mockSchema.ts | 3 +- 4 files changed, 125 insertions(+), 35 deletions(-) diff --git a/src/lib/encoder.test.ts b/src/lib/encoder.test.ts index 8ddf60d3..55334fdb 100644 --- a/src/lib/encoder.test.ts +++ b/src/lib/encoder.test.ts @@ -145,16 +145,35 @@ describe('encoder', () => { encodedValue: '0x7765656b', // utf8-encoded characters decodedValue: '0x7765656b', }, + ]; + + oneWayEncodingTestCases.forEach((testCase) => { + it(`encodes one way \`input\` = ${testCase.input} as ${testCase.valueType}, but does not decode back as the same input`, async () => { + const encodedValue = encodeValueType( + testCase.valueType, + testCase.input, + ); + + assert.deepStrictEqual(encodedValue, testCase.encodedValue); + assert.deepStrictEqual( + decodeValueType(testCase.valueType, encodedValue), + testCase.decodedValue, + ); + }); + }); + + const leftPaddedTestCases = [ { valueType: 'bytes4', input: 1122334455, - encodedValue: '0x42e576f7', // number converted to hex + right padded + encodedValue: '0x42e576f7', // number converted to hex + left padded still decodedValue: '0x42e576f7', }, ]; - oneWayEncodingTestCases.forEach((testCase) => { - it(`encodes one way \`input\` = ${testCase.input} as ${testCase.valueType}, but does not decode back as the same input`, async () => { + // numbers encoded as `bytesN` are left padded to allow symmetric encoding / decoding + leftPaddedTestCases.forEach((testCase) => { + it(`encodes + left pad numbers \`input\` = ${testCase.input} as ${testCase.valueType} padded on the left with \`00\`s`, async () => { const encodedValue = encodeValueType( testCase.valueType, testCase.input, @@ -273,18 +292,36 @@ describe('encoder', () => { encodedValue: '0x546869732073656e74656e6365206973203332206279746573206c6f6e672021', }, + ]; + + oneWayEncodingTestCases.forEach((testCase) => { + it(`encodes one way \`input\` = ${testCase.input} as ${testCase.valueType}, but does not decode back as the same input`, async () => { + const encodedValue = encodeValueType( + testCase.valueType, + testCase.input, + ); + + assert.deepStrictEqual(encodedValue, testCase.encodedValue); + assert.deepStrictEqual( + decodeValueType(testCase.valueType, encodedValue), + testCase.decodedValue, + ); + }); + }); + + const leftPaddedTestCases = [ { valueType: 'bytes32', input: 12345, decodedValue: - '0x3039000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000003039', encodedValue: - '0x3039000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000000000000000000000000000000000000000003039', }, ]; - oneWayEncodingTestCases.forEach((testCase) => { - it(`encodes one way \`input\` = ${testCase.input} as ${testCase.valueType}, but does not decode back as the same input`, async () => { + leftPaddedTestCases.forEach((testCase) => { + it(`encodes + left pad \`input\` = ${testCase.input} as ${testCase.valueType} padded on the right with \`00\`s`, async () => { const encodedValue = encodeValueType( testCase.valueType, testCase.input, diff --git a/src/lib/encoder.ts b/src/lib/encoder.ts index c5d12d11..74f30891 100644 --- a/src/lib/encoder.ts +++ b/src/lib/encoder.ts @@ -57,17 +57,18 @@ import { countNumberOfBytes, isValidUintSize, countSignificantBits, + isValidBytesSize, } from './utils'; import { ERC725JSONSchemaValueType } from '../types/ERC725JSONSchema'; const abiCoder = AbiCoder; const uintNValueTypeRegex = /^uint(\d+)$/; - +``; +const bytesNValueTypeRegex = /^bytes(\d+)$/; +``; const BytesNValueContentRegex = /Bytes(\d+)/; -const ALLOWED_BYTES_SIZES = [2, 4, 8, 16, 32, 64, 128, 256]; - export const encodeDataSourceWithHash = ( verification: undefined | Verification, dataSource: string, @@ -171,23 +172,58 @@ export const decodeDataSourceWithHash = (value: string): URLDataWithHash => { }; }; +type BytesNValueTypes = + | 'bytes1' + | 'bytes2' + | 'bytes3' + | 'bytes4' + | 'bytes5' + | 'bytes6' + | 'bytes7' + | 'bytes8' + | 'bytes9' + | 'bytes10' + | 'bytes11' + | 'bytes12' + | 'bytes13' + | 'bytes14' + | 'bytes15' + | 'bytes16' + | 'bytes17' + | 'bytes18' + | 'bytes19' + | 'bytes20' + | 'bytes21' + | 'bytes22' + | 'bytes23' + | 'bytes24' + | 'bytes25' + | 'bytes26' + | 'bytes27' + | 'bytes28' + | 'bytes29' + | 'bytes30' + | 'bytes31' + | 'bytes32'; + const encodeToBytesN = ( - bytesN: 'bytes32' | 'bytes4', + bytesN: BytesNValueTypes, value: string | number, ): string => { + const numberOfBytesInType = parseInt(bytesN.split('bytes')[1], 10); + let valueToEncode: string; if (typeof value === 'string' && !isHex(value)) { // if we receive a plain string (e.g: "hey!"), convert it to utf8-hex data valueToEncode = toHex(value); } else if (typeof value === 'number') { - // if we receive a number as input, convert it to hex - valueToEncode = numberToHex(value); + // if we receive a number as input, convert it to hex, left padded + valueToEncode = padLeft(numberToHex(value), numberOfBytesInType * 2); } else { valueToEncode = value; } - const numberOfBytesInType = Number.parseInt(bytesN.split('bytes')[1], 10); const numberOfBytesInValue = countNumberOfBytes(valueToEncode); if (numberOfBytesInValue > numberOfBytesInType) { @@ -204,7 +240,7 @@ const encodeToBytesN = ( } const bytesArray = hexToBytes(abiEncodedValue); - return bytesToHex(bytesArray.slice(0, 4)); + return bytesToHex(bytesArray.slice(0, numberOfBytesInType)); }; /** @@ -441,6 +477,10 @@ const valueTypeEncodingMap = ( decode: (value: string) => any; } => { const uintNRegexMatch = type.match(uintNValueTypeRegex); + const bytesNRegexMatch = type.match(bytesNValueTypeRegex); + const bytesLength = bytesNRegexMatch + ? Number.parseInt(bytesNRegexMatch[1], 10) + : ''; const uintLength = uintNRegexMatch ? Number.parseInt(uintNRegexMatch[0].slice(4), 10) @@ -463,6 +503,13 @@ const valueTypeEncodingMap = ( return compactBytesArrayMap[type]; } + if (type === 'bytes') { + return { + encode: (value: string) => toHex(value), + decode: (value: string) => value, + }; + } + switch (type) { case 'bool': case 'boolean': @@ -552,27 +599,24 @@ const valueTypeEncodingMap = ( return toBN(value).toNumber(); }, }; - case 'bytes32': + case `bytes${bytesLength}`: return { - encode: (value: string | number) => encodeToBytesN('bytes32', value), - decode: (value: string) => abiCoder.decodeParameter('bytes32', value), - }; - case 'bytes4': - return { - encode: (value: string | number) => encodeToBytesN('bytes4', value), + encode: (value: string | number) => { + if (!isValidBytesSize(bytesLength as number)) { + throw new Error( + `Can't encode ${value} as ${type}. Invalid \`bytesN\` provided. Expected a \`N\` value for bytesN between 1 and 32.`, + ); + } + return encodeToBytesN(type as BytesNValueTypes, value); + }, decode: (value: string) => { // we need to abi-encode the value again to ensure that: - // - that data to decode does not go over 4 bytes. - // - if the data is less than 4 bytes, that it gets padded to 4 bytes long. - const reEncodedData = abiCoder.encodeParameter('bytes4', value); - return abiCoder.decodeParameter('bytes4', reEncodedData); + // - that data to decode does not go over N bytes. + // - if the data is less than N bytes, that it gets padded to N bytes long. + const reEncodedData = abiCoder.encodeParameter(type, value); + return abiCoder.decodeParameter(type, reEncodedData); }, }; - case 'bytes': - return { - encode: (value: string) => toHex(value), - decode: (value: string) => value, - }; case 'bool[]': return { encode: (value: boolean) => abiCoder.encodeParameter('bool[]', value), @@ -778,7 +822,7 @@ export const valueContentEncodingMap = ( throw new Error(`Value: ${value} is not hex.`); } - if (bytesLength && !ALLOWED_BYTES_SIZES.includes(bytesLength)) { + if (bytesLength && !isValidBytesSize(bytesLength)) { throw new Error( `Provided bytes length: ${bytesLength} for encoding valueContent: ${valueContent} is not valid.`, ); @@ -800,7 +844,7 @@ export const valueContentEncodingMap = ( return null; } - if (bytesLength && !ALLOWED_BYTES_SIZES.includes(bytesLength)) { + if (bytesLength && !isValidBytesSize(bytesLength)) { console.error( `Provided bytes length: ${bytesLength} for encoding valueContent: ${valueContent} is not valid.`, ); diff --git a/src/lib/utils.ts b/src/lib/utils.ts index c7fb78db..e22545a2 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -816,3 +816,13 @@ export const duplicateMultiTypeERC725SchemaEntry = ( export function isValidUintSize(bitSize: number) { return bitSize >= 8 && bitSize <= 256 && bitSize % 8 === 0; } + +/* + * `bytesN` must be a valid number of bytes between 1 and 32 + * e.g: bytes1, bytes2, bytes3, bytes4, ..., bytes32 + * + * @param bytesSize the size of the fixed size bytes value + */ +export function isValidBytesSize(bytesSize: number) { + return bytesSize >= 1 && bytesSize <= 32; +} diff --git a/test/mockSchema.ts b/test/mockSchema.ts index ffd8829f..485b778e 100644 --- a/test/mockSchema.ts +++ b/test/mockSchema.ts @@ -357,8 +357,7 @@ export const mockSchema: (ERC725JSONSchema & { returnRawDataArray: abiCoder.encodeParameter('bytes[]', [ '0x0000000000000000000000000000000000000000000000000000000000000063', ]), - returnGraphData: - '0x0000000000000000000000000000000000000000000000000000000000000063', + returnGraphData: '0x63', expectedResult: 99, }, From 3e6213a3acf3b5d16b79447f87a7029f1020d7b3 Mon Sep 17 00:00:00 2001 From: CJ42 Date: Thu, 25 Apr 2024 18:02:04 +0100 Subject: [PATCH 3/5] refactor: improve code for `Literal` hex for `valueContent` --- src/lib/encoder.ts | 18 ++++++++++++------ src/lib/utils.test.ts | 2 +- src/lib/utils.ts | 16 +++++++++++----- 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/lib/encoder.ts b/src/lib/encoder.ts index 74f30891..bed0761d 100644 --- a/src/lib/encoder.ts +++ b/src/lib/encoder.ts @@ -57,7 +57,8 @@ import { countNumberOfBytes, isValidUintSize, countSignificantBits, - isValidBytesSize, + isValidByteSize, + isValueContentLiteralHex, } from './utils'; import { ERC725JSONSchemaValueType } from '../types/ERC725JSONSchema'; @@ -602,7 +603,7 @@ const valueTypeEncodingMap = ( case `bytes${bytesLength}`: return { encode: (value: string | number) => { - if (!isValidBytesSize(bytesLength as number)) { + if (!isValidByteSize(bytesLength as number)) { throw new Error( `Can't encode ${value} as ${type}. Invalid \`bytesN\` provided. Expected a \`N\` value for bytesN between 1 and 32.`, ); @@ -822,7 +823,7 @@ export const valueContentEncodingMap = ( throw new Error(`Value: ${value} is not hex.`); } - if (bytesLength && !isValidBytesSize(bytesLength)) { + if (bytesLength && !isValidByteSize(bytesLength)) { throw new Error( `Provided bytes length: ${bytesLength} for encoding valueContent: ${valueContent} is not valid.`, ); @@ -844,7 +845,7 @@ export const valueContentEncodingMap = ( return null; } - if (bytesLength && !isValidBytesSize(bytesLength)) { + if (bytesLength && !isValidByteSize(bytesLength)) { console.error( `Provided bytes length: ${bytesLength} for encoding valueContent: ${valueContent} is not valid.`, ); @@ -981,8 +982,13 @@ export function decodeValueContent( valueContent: string, value: string, ): string | URLDataWithHash | number | boolean | null { - if (valueContent.slice(0, 2) === '0x') { - return valueContent === value ? value : 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; } if (value == null || value === '0x') { diff --git a/src/lib/utils.test.ts b/src/lib/utils.test.ts index 088dfe19..3f994707 100644 --- a/src/lib/utils.test.ts +++ b/src/lib/utils.test.ts @@ -378,7 +378,7 @@ describe('utils', () => { valueContent: '0xc9aaAE3201F40fd0fF04D9c885769d8256A456ab', valueType: 'bytes', decodedValue: '0xc9aaAE3201F40fd0fF04D9c885769d8256A456ab', - encodedValue: '0xc9aaAE3201F40fd0fF04D9c885769d8256A456ab', + encodedValue: '0xc9aaae3201f40fd0ff04d9c885769d8256a456ab', // encoded hex is always lower case }, ]; diff --git a/src/lib/utils.ts b/src/lib/utils.ts index e22545a2..e8e49baf 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -23,6 +23,7 @@ import { checkAddressChecksum, hexToBytes, isAddress, + isHexStrict, leftPad, numberToHex, padLeft, @@ -61,10 +62,6 @@ import { EncodeDataInput } from '../types/decodeData'; import { GetDataDynamicKey } from '../types/GetData'; import { isValidTuple } from './decodeData'; -function isValueContentLiteralHex(valueContent: string): boolean { - return valueContent.slice(0, 2) === '0x'; -} - /** * * @param {string} valueContent as per ERC725Schema definition @@ -823,6 +820,15 @@ export function isValidUintSize(bitSize: number) { * * @param bytesSize the size of the fixed size bytes value */ -export function isValidBytesSize(bytesSize: number) { +export function isValidByteSize(bytesSize: number) { return bytesSize >= 1 && bytesSize <= 32; } + +/** + * @dev Check if the `valueContent` in a schema is defined as an hex literal string + * @param valueContent The valueContent part of a schema + * @returns true or false + */ +export function isValueContentLiteralHex(valueContent: string): boolean { + return isHexStrict(valueContent); +} From 5af2f584d5a18419a21e4d48881ea03656079947 Mon Sep 17 00:00:00 2001 From: CJ42 Date: Thu, 25 Apr 2024 18:08:15 +0100 Subject: [PATCH 4/5] refactor: cleanup code --- src/index.test.ts | 4 ---- src/lib/encoder.test.ts | 2 +- src/lib/encoder.ts | 4 +++- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/index.test.ts b/src/index.test.ts index a872ac31..25e2c9ca 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -939,10 +939,6 @@ describe('Running @erc725/erc725.js tests...', () => { }); }); - describe('Testing `decodeData`', () => { - // ... - }); - describe('Testing utility encoding & decoding functions', () => { const allGraphData = generateAllData(mockSchema) as any; /* **************************************** */ diff --git a/src/lib/encoder.test.ts b/src/lib/encoder.test.ts index 55334fdb..4f8d02b5 100644 --- a/src/lib/encoder.test.ts +++ b/src/lib/encoder.test.ts @@ -321,7 +321,7 @@ describe('encoder', () => { ]; leftPaddedTestCases.forEach((testCase) => { - it(`encodes + left pad \`input\` = ${testCase.input} as ${testCase.valueType} padded on the right with \`00\`s`, async () => { + it(`encodes + left pad number \`input\` = ${testCase.input} as ${testCase.valueType} padded on the left with \`00\`s`, async () => { const encodedValue = encodeValueType( testCase.valueType, testCase.input, diff --git a/src/lib/encoder.ts b/src/lib/encoder.ts index bed0761d..56f71c4e 100644 --- a/src/lib/encoder.ts +++ b/src/lib/encoder.ts @@ -219,7 +219,9 @@ const encodeToBytesN = ( // if we receive a plain string (e.g: "hey!"), convert it to utf8-hex data valueToEncode = toHex(value); } else if (typeof value === 'number') { - // if we receive a number as input, convert it to hex, left padded + // if we receive a number as input, convert it to hex, + // despite `bytesN` pads on the right, we pad number on the left side here + // to symmetrically encode / decode valueToEncode = padLeft(numberToHex(value), numberOfBytesInType * 2); } else { valueToEncode = value; From b926597b95294d24c637bd0d344c0329ba5817a9 Mon Sep 17 00:00:00 2001 From: CJ42 Date: Thu, 25 Apr 2024 18:14:50 +0100 Subject: [PATCH 5/5] chore: fix linter errors --- src/lib/encoder.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/lib/encoder.ts b/src/lib/encoder.ts index 56f71c4e..bf23c8b0 100644 --- a/src/lib/encoder.ts +++ b/src/lib/encoder.ts @@ -65,9 +65,7 @@ import { ERC725JSONSchemaValueType } from '../types/ERC725JSONSchema'; const abiCoder = AbiCoder; const uintNValueTypeRegex = /^uint(\d+)$/; -``; const bytesNValueTypeRegex = /^bytes(\d+)$/; -``; const BytesNValueContentRegex = /Bytes(\d+)/; export const encodeDataSourceWithHash = ( @@ -211,7 +209,7 @@ const encodeToBytesN = ( bytesN: BytesNValueTypes, value: string | number, ): string => { - const numberOfBytesInType = parseInt(bytesN.split('bytes')[1], 10); + const numberOfBytesInType = Number.parseInt(bytesN.split('bytes')[1], 10); let valueToEncode: string; @@ -985,7 +983,7 @@ export function decodeValueContent( value: string, ): string | URLDataWithHash | number | boolean | null { if (isValueContentLiteralHex(valueContent)) { - if (valueContent.toLowerCase() != value) { + 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`, );