From ef30c6c7cbec192487708ea85e31e0ac3d88fd6a Mon Sep 17 00:00:00 2001 From: CJ42 Date: Mon, 13 May 2024 17:25:22 +0100 Subject: [PATCH 1/7] refactor: remove custom method in favour of web3js lib function --- src/lib/detector.ts | 24 +++--------------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/src/lib/detector.ts b/src/lib/detector.ts index 1fae16a1..359772ad 100644 --- a/src/lib/detector.ts +++ b/src/lib/detector.ts @@ -21,6 +21,7 @@ * @date 2022 */ +import { isHexStrict } from 'web3-utils'; import { LSP6_DEFAULT_PERMISSIONS } from '../constants/constants'; import { @@ -57,22 +58,6 @@ export const internalSupportsInterface = async ( } }; -/** - * @notice Check if the given string is a valid 32-byte hex string. - * @param str The string to be checked. - * @return A boolean value indicating whether the string is a valid 32-byte hex string. - */ -function isValid32ByteHexString(str: string): boolean { - return ( - str.startsWith('0x') && - str.length === 66 && - str - .slice(2) - .split('') - .every((char) => '0123456789abcdefABCDEF'.includes(char)) - ); -} - /** * @notice Map a permission to its corresponding bytes32 representation. * @param permission The permission string to be mapped. @@ -80,10 +65,7 @@ function isValid32ByteHexString(str: string): boolean { * @dev Throws an error if the input is not a known permission name or a valid 32-byte hex string. */ function mapPermission(permission: string): string { - if ( - !LSP6_DEFAULT_PERMISSIONS[permission] && - !isValid32ByteHexString(permission) - ) { + if (!LSP6_DEFAULT_PERMISSIONS[permission] && !isHexStrict(permission)) { throw new Error( 'Invalid permission string. It must be a valid 32-byte hex string or a known permission name.', ); @@ -103,7 +85,7 @@ export const checkPermissions = ( grantedPermissions: string, ): boolean => { // Validate the grantedPermissions string - if (!isValid32ByteHexString(grantedPermissions)) { + if (!isHexStrict(grantedPermissions)) { throw new Error( 'Invalid grantedPermissions string. It must be a valid 32-byte hex string.', ); From 63016d63997f3559b4b5f32199ad2af1dc4fde06 Mon Sep 17 00:00:00 2001 From: CJ42 Date: Mon, 13 May 2024 17:25:41 +0100 Subject: [PATCH 2/7] test: add extra test case for encoding number `0` as `uintN` --- src/lib/encoder.test.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/lib/encoder.test.ts b/src/lib/encoder.test.ts index 4f8d02b5..aa6b62f0 100644 --- a/src/lib/encoder.test.ts +++ b/src/lib/encoder.test.ts @@ -409,6 +409,11 @@ describe('encoder', () => { decodedValue: 11, encodedValue: '0x0000000000000000000000000000000b', }, + { + valueType: 'uint128', + decodedValue: 0, + encodedValue: '0x00000000000000000000000000000000', + }, ]; validTestCases.forEach((testCase) => { From 08e72d61f37d8e3d8b70b5b0bf7cb355687f3546 Mon Sep 17 00:00:00 2001 From: CJ42 Date: Mon, 13 May 2024 17:47:47 +0100 Subject: [PATCH 3/7] feat: export `mapPermission` function and don't make it throw an error --- src/index.ts | 22 +++++++++++++++++++++- src/lib/detector.ts | 17 ++++++++++------- 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/src/index.ts b/src/index.ts index d571f779..2f061268 100644 --- a/src/index.ts +++ b/src/index.ts @@ -67,7 +67,11 @@ import { encodeValueContent, decodeValueContent, } from './lib/encoder'; -import { internalSupportsInterface, checkPermissions } from './lib/detector'; +import { + internalSupportsInterface, + checkPermissions, + mapPermission, +} from './lib/detector'; import { decodeMappingKey } from './lib/decodeMappingKey'; import { encodePermissions, decodePermissions } from './lib/permissions'; import { AssetURLEncode } from './types/encodeData'; @@ -664,6 +668,22 @@ export class ERC725 { return checkPermissions(requiredPermissions, grantedPermissions); } + static mapPermission(permission: string): string | null { + return mapPermission(permission); + } + + mapPermission(permission: string): string | null { + return mapPermission(permission); + } + + static mapPermission(permission: string): string | null { + return mapPermission(permission); + } + + mapPermission(permission: string): string | null { + return mapPermission(permission); + } + encodeDataSourceWithHash( verification: undefined | Verification, dataSource: string, diff --git a/src/lib/detector.ts b/src/lib/detector.ts index 359772ad..35df9a67 100644 --- a/src/lib/detector.ts +++ b/src/lib/detector.ts @@ -62,13 +62,11 @@ export const internalSupportsInterface = async ( * @notice Map a permission to its corresponding bytes32 representation. * @param permission The permission string to be mapped. * @return The bytes32 representation of the permission. - * @dev Throws an error if the input is not a known permission name or a valid 32-byte hex string. + * @dev Return null if the input is not a known permission name or a valid 32-byte hex string. */ -function mapPermission(permission: string): string { +export function mapPermission(permission: string): string | null { if (!LSP6_DEFAULT_PERMISSIONS[permission] && !isHexStrict(permission)) { - throw new Error( - 'Invalid permission string. It must be a valid 32-byte hex string or a known permission name.', - ); + return null; } return LSP6_DEFAULT_PERMISSIONS[permission] || permission; } @@ -97,11 +95,16 @@ export const checkPermissions = ( : [requiredPermissions]; // Map the literal permissions to their bytes32 representation - const mappedPermissionArray: string[] = + const mappedPermissionArray: (string | null)[] = requiredPermissionArray.map(mapPermission); // Perform the AND operation check for each required permission - return mappedPermissionArray.every((requiredPermission: string) => { + return mappedPermissionArray.every((requiredPermission: string | null) => { + if (!requiredPermission) { + throw new Error( + `Invalid permission string: ${requiredPermission}. It must be a valid 32-byte hex string or a known permission name.`, + ); + } const requiredPermissionBigInt = BigInt(requiredPermission); const grantedPermissionsBigInt = BigInt(grantedPermissions); From 07e123a8670297f8e748a80dda6aefbf67076cdb Mon Sep 17 00:00:00 2001 From: CJ42 Date: Mon, 13 May 2024 17:49:09 +0100 Subject: [PATCH 4/7] chore: group together permissions functions in the ERC725 class --- src/index.ts | 91 +++++++++++++++++++++++++++------------------------- 1 file changed, 47 insertions(+), 44 deletions(-) diff --git a/src/index.ts b/src/index.ts index 2f061268..cd2ce112 100644 --- a/src/index.ts +++ b/src/index.ts @@ -506,50 +506,6 @@ export class ERC725 { ); } - /** - * Encode permissions into a hexadecimal string as defined by the LSP6 KeyManager Standard. - * - * @link https://github.com/lukso-network/LIPs/blob/main/LSPs/LSP-6-KeyManager.md LSP6 KeyManager Standard. - * @param permissions The permissions you want to specify to be included or excluded. Any ommitted permissions will default to false. - * @returns {*} The permissions encoded as a hexadecimal string as defined by the LSP6 Standard. - */ - static encodePermissions(permissions: Permissions): string { - return encodePermissions(permissions); - } - - /** - * Encode permissions into a hexadecimal string as defined by the LSP6 KeyManager Standard. - * - * @link https://github.com/lukso-network/LIPs/blob/main/LSPs/LSP-6-KeyManager.md LSP6 KeyManager Standard. - * @param permissions The permissions you want to specify to be included or excluded. Any ommitted permissions will default to false. - * @returns {*} The permissions encoded as a hexadecimal string as defined by the LSP6 Standard. - */ - encodePermissions(permissions: Permissions): string { - return encodePermissions(permissions); - } - - /** - * Decodes permissions from hexadecimal as defined by the LSP6 KeyManager Standard. - * - * @link https://github.com/lukso-network/LIPs/blob/main/LSPs/LSP-6-KeyManager.md LSP6 KeyManager Standard. - * @param permissionHex The permission hexadecimal value to be decoded. - * @returns Object specifying whether default LSP6 permissions are included in provided hexademical string. - */ - static decodePermissions(permissionHex: string) { - return decodePermissions(permissionHex); - } - - /** - * Decodes permissions from hexadecimal as defined by the LSP6 KeyManager Standard. - * - * @link https://github.com/lukso-network/LIPs/blob/main/LSPs/LSP-6-KeyManager.md LSP6 KeyManager Standard. - * @param permissionHex The permission hexadecimal value to be decoded. - * @returns Object specifying whether default LSP6 permissions are included in provided hexademical string. - */ - decodePermissions(permissionHex: string) { - return decodePermissions(permissionHex); - } - /** * Hashes a key name for use on an ERC725Y contract according to LSP2 ERC725Y JSONSchema standard. * @@ -638,6 +594,53 @@ export class ERC725 { return supportsInterface(interfaceIdOrName, options); } + // Permissions related functions + // ----------------------------- + + /** + * Encode permissions into a hexadecimal string as defined by the LSP6 KeyManager Standard. + * + * @link https://github.com/lukso-network/LIPs/blob/main/LSPs/LSP-6-KeyManager.md LSP6 KeyManager Standard. + * @param permissions The permissions you want to specify to be included or excluded. Any ommitted permissions will default to false. + * @returns {*} The permissions encoded as a hexadecimal string as defined by the LSP6 Standard. + */ + static encodePermissions(permissions: Permissions): string { + return encodePermissions(permissions); + } + + /** + * Encode permissions into a hexadecimal string as defined by the LSP6 KeyManager Standard. + * + * @link https://github.com/lukso-network/LIPs/blob/main/LSPs/LSP-6-KeyManager.md LSP6 KeyManager Standard. + * @param permissions The permissions you want to specify to be included or excluded. Any ommitted permissions will default to false. + * @returns {*} The permissions encoded as a hexadecimal string as defined by the LSP6 Standard. + */ + encodePermissions(permissions: Permissions): string { + return encodePermissions(permissions); + } + + /** + * Decodes permissions from hexadecimal as defined by the LSP6 KeyManager Standard. + * + * @link https://github.com/lukso-network/LIPs/blob/main/LSPs/LSP-6-KeyManager.md LSP6 KeyManager Standard. + * @param permissionHex The permission hexadecimal value to be decoded. + * @returns Object specifying whether default LSP6 permissions are included in provided hexademical string. + */ + static decodePermissions(permissionHex: string) { + return decodePermissions(permissionHex); + } + + /** + * Decodes permissions from hexadecimal as defined by the LSP6 KeyManager Standard. + * + * @link https://github.com/lukso-network/LIPs/blob/main/LSPs/LSP-6-KeyManager.md LSP6 KeyManager Standard. + * @param permissionHex The permission hexadecimal value to be decoded. + * @returns Object specifying whether default LSP6 permissions are included in provided hexademical string. + */ + decodePermissions(permissionHex: string) { + return decodePermissions(permissionHex); + } + /** * Check if the required permissions are included in the granted permissions as defined by the LSP6 KeyManager Standard. * From 8b2bb98025e7cb4d76e59a6960b6252b5a9c5d55 Mon Sep 17 00:00:00 2001 From: CJ42 Date: Mon, 13 May 2024 17:55:15 +0100 Subject: [PATCH 5/7] refactor: move all permissions functionalities under `permissions.ts` --- src/index.ts | 25 ++++++++---------- src/lib/detector.ts | 60 ------------------------------------------ src/lib/permissions.ts | 59 ++++++++++++++++++++++++++++++++++++++++- 3 files changed, 69 insertions(+), 75 deletions(-) diff --git a/src/index.ts b/src/index.ts index cd2ce112..28de3ffe 100644 --- a/src/index.ts +++ b/src/index.ts @@ -67,13 +67,14 @@ import { encodeValueContent, decodeValueContent, } from './lib/encoder'; +import { internalSupportsInterface } from './lib/detector'; +import { decodeMappingKey } from './lib/decodeMappingKey'; import { - internalSupportsInterface, + encodePermissions, + decodePermissions, checkPermissions, mapPermission, -} from './lib/detector'; -import { decodeMappingKey } from './lib/decodeMappingKey'; -import { encodePermissions, decodePermissions } from './lib/permissions'; +} from './lib/permissions'; import { AssetURLEncode } from './types/encodeData'; import { URLDataToEncode, URLDataWithHash, Verification } from './types'; @@ -99,8 +100,12 @@ export { decodeValueContent, } from './lib/encoder'; export { getDataFromExternalSources } from './lib/getDataFromExternalSources'; -export { encodePermissions, decodePermissions } from './lib/permissions'; -export { checkPermissions } from './lib/detector'; +export { + encodePermissions, + decodePermissions, + checkPermissions, + mapPermission, +} from './lib/permissions'; export { getSchema } from './lib/schemaParser'; // PRIVATE FUNCTION @@ -679,14 +684,6 @@ export class ERC725 { return mapPermission(permission); } - static mapPermission(permission: string): string | null { - return mapPermission(permission); - } - - mapPermission(permission: string): string | null { - return mapPermission(permission); - } - encodeDataSourceWithHash( verification: undefined | Verification, dataSource: string, diff --git a/src/lib/detector.ts b/src/lib/detector.ts index 35df9a67..e7a6e1b9 100644 --- a/src/lib/detector.ts +++ b/src/lib/detector.ts @@ -21,9 +21,6 @@ * @date 2022 */ -import { isHexStrict } from 'web3-utils'; -import { LSP6_DEFAULT_PERMISSIONS } from '../constants/constants'; - import { AddressProviderOptions, INTERFACE_IDS_0_12_0, @@ -57,60 +54,3 @@ export const internalSupportsInterface = async ( throw new Error(`Error checking the interface: ${error}`); } }; - -/** - * @notice Map a permission to its corresponding bytes32 representation. - * @param permission The permission string to be mapped. - * @return The bytes32 representation of the permission. - * @dev Return null if the input is not a known permission name or a valid 32-byte hex string. - */ -export function mapPermission(permission: string): string | null { - if (!LSP6_DEFAULT_PERMISSIONS[permission] && !isHexStrict(permission)) { - return null; - } - return LSP6_DEFAULT_PERMISSIONS[permission] || permission; -} - -/** - * @notice Check if the required permissions are included in the granted permissions. - * @param requiredPermissions An array of required permissions or a single required permission. - * @param grantedPermissions The granted permissions as a 32-byte hex string. - * @return A boolean value indicating whether the required permissions are included in the granted permissions. - * @dev Throws an error if the grantedPermissions input is not a valid 32-byte hex string. - */ -export const checkPermissions = ( - requiredPermissions: string[] | string, - grantedPermissions: string, -): boolean => { - // Validate the grantedPermissions string - if (!isHexStrict(grantedPermissions)) { - throw new Error( - 'Invalid grantedPermissions string. It must be a valid 32-byte hex string.', - ); - } - - // Convert requiredPermissions to an array if it's a single string - const requiredPermissionArray: string[] = Array.isArray(requiredPermissions) - ? requiredPermissions - : [requiredPermissions]; - - // Map the literal permissions to their bytes32 representation - const mappedPermissionArray: (string | null)[] = - requiredPermissionArray.map(mapPermission); - - // Perform the AND operation check for each required permission - return mappedPermissionArray.every((requiredPermission: string | null) => { - if (!requiredPermission) { - throw new Error( - `Invalid permission string: ${requiredPermission}. It must be a valid 32-byte hex string or a known permission name.`, - ); - } - const requiredPermissionBigInt = BigInt(requiredPermission); - const grantedPermissionsBigInt = BigInt(grantedPermissions); - - return ( - (requiredPermissionBigInt & grantedPermissionsBigInt) === - requiredPermissionBigInt - ); - }); -}; diff --git a/src/lib/permissions.ts b/src/lib/permissions.ts index b35fb55e..2d680119 100644 --- a/src/lib/permissions.ts +++ b/src/lib/permissions.ts @@ -1,4 +1,4 @@ -import { hexToNumber, leftPad, numberToHex } from 'web3-utils'; +import { hexToNumber, isHexStrict, leftPad, numberToHex } from 'web3-utils'; import { LSP6_DEFAULT_PERMISSIONS } from '../constants/constants'; import { Permissions } from '../types/Method'; @@ -80,3 +80,60 @@ export function decodePermissions(permissionHex: string) { return result; } + +/** + * @notice Map a permission to its corresponding bytes32 representation. + * @param permission The permission string to be mapped. + * @return The bytes32 representation of the permission. + * @dev Return null if the input is not a known permission name or a valid 32-byte hex string. + */ +export function mapPermission(permission: string): string | null { + if (!LSP6_DEFAULT_PERMISSIONS[permission] && !isHexStrict(permission)) { + return null; + } + return LSP6_DEFAULT_PERMISSIONS[permission] || permission; +} + +/** + * @notice Check if the required permissions are included in the granted permissions. + * @param requiredPermissions An array of required permissions or a single required permission. + * @param grantedPermissions The granted permissions as a 32-byte hex string. + * @return A boolean value indicating whether the required permissions are included in the granted permissions. + * @dev Throws an error if the grantedPermissions input is not a valid 32-byte hex string. + */ +export const checkPermissions = ( + requiredPermissions: string[] | string, + grantedPermissions: string, +): boolean => { + // Validate the grantedPermissions string + if (!isHexStrict(grantedPermissions)) { + throw new Error( + 'Invalid grantedPermissions string. It must be a valid 32-byte hex string.', + ); + } + + // Convert requiredPermissions to an array if it's a single string + const requiredPermissionArray: string[] = Array.isArray(requiredPermissions) + ? requiredPermissions + : [requiredPermissions]; + + // Map the literal permissions to their bytes32 representation + const mappedPermissionArray: (string | null)[] = + requiredPermissionArray.map(mapPermission); + + // Perform the AND operation check for each required permission + return mappedPermissionArray.every((requiredPermission: string | null) => { + if (!requiredPermission) { + throw new Error( + `Invalid permission string: ${requiredPermission}. It must be a valid 32-byte hex string or a known permission name.`, + ); + } + const requiredPermissionBigInt = BigInt(requiredPermission); + const grantedPermissionsBigInt = BigInt(grantedPermissions); + + return ( + (requiredPermissionBigInt & grantedPermissionsBigInt) === + requiredPermissionBigInt + ); + }); +}; From fca2ea53af20f5d45975b238b161c1eec09fb0b9 Mon Sep 17 00:00:00 2001 From: CJ42 Date: Fri, 17 May 2024 09:31:50 +0200 Subject: [PATCH 6/7] test: move `permissions.test.ts` file --- src/lib/detector.test.ts | 164 +----------------------------------- src/lib/permissions.test.ts | 164 ++++++++++++++++++++++++++++++++++++ 2 files changed, 165 insertions(+), 163 deletions(-) create mode 100644 src/lib/permissions.test.ts diff --git a/src/lib/detector.test.ts b/src/lib/detector.test.ts index d397feae..ac4afd50 100644 --- a/src/lib/detector.test.ts +++ b/src/lib/detector.test.ts @@ -24,7 +24,7 @@ import { expect } from 'chai'; import * as sinon from 'sinon'; import { INTERFACE_IDS_0_12_0 } from '../constants/interfaces'; -import { internalSupportsInterface, checkPermissions } from './detector'; +import { internalSupportsInterface } from './detector'; describe('supportsInterface', () => { it('it should return true if the contract supports the interface with name', async () => { @@ -66,165 +66,3 @@ describe('supportsInterface', () => { expect(doesSupportInterface).to.be.true; }); }); - -describe('checkPermissions', () => { - describe('test with single permission', () => { - it('should throw an error when given an invalid permission string', async () => { - const requiredPermissions = 'INVALIDPERMISSION'; - const grantedPermissions = - '0x000000000000000000000000000000000000000000000000000000000000ff51'; - expect(() => - checkPermissions(requiredPermissions, grantedPermissions), - ).to.throw( - 'Invalid permission string. It must be a valid 32-byte hex string or a known permission name.', - ); - }); - - it('should throw an error when given an invalid 32-byte hex string', async () => { - const requiredPermissions = '0xinvalidhexstring'; - const grantedPermissions = - '0x000000000000000000000000000000000000000000000000000000000000ff51'; - expect(() => - checkPermissions(requiredPermissions, grantedPermissions), - ).to.throw( - 'Invalid permission string. It must be a valid 32-byte hex string or a known permission name.', - ); - }); - - it('should throw an error when given an invalid grantedPermission 32-byte hex string', async () => { - const requiredPermissions = 'CHANGEOWNER'; - const grantedPermissions = '0xinvalidgrantedpermissionhexstring'; - expect(() => - checkPermissions(requiredPermissions, grantedPermissions), - ).to.throw( - 'Invalid grantedPermissions string. It must be a valid 32-byte hex string.', - ); - }); - - it('should return true when single literal permission matches granted permissions', async () => { - const requiredPermissions = 'CHANGEOWNER'; - const grantedPermissions = - '0x000000000000000000000000000000000000000000000000000000000000ff51'; - const result = checkPermissions(requiredPermissions, grantedPermissions); - expect(result).to.be.true; - }); - - it('should return true when single bytes32 permission matches granted permissions', async () => { - const requiredPermissions = - '0x0000000000000000000000000000000000000000000000000000000000000001'; - const grantedPermissions = - '0x000000000000000000000000000000000000000000000000000000000000ff51'; - const result = checkPermissions(requiredPermissions, grantedPermissions); - expect(result).to.be.true; - }); - - it('should return false when single bytes32 permission does not match granted permissions', async () => { - const requiredPermissions = - '0x0000000000000000000000000000000000000000000000000000000000000001'; - const grantedPermissions = - '0x000000000000000000000000000000000000000000000000000000000000fff2'; - const result = checkPermissions(requiredPermissions, grantedPermissions); - expect(result).to.be.false; - }); - - it('should return false when single literal permission does not match granted permissions', async () => { - const requiredPermissions = 'CHANGEOWNER'; - const grantedPermissions = - '0x000000000000000000000000000000000000000000000000000000000000fff2'; - const result = checkPermissions(requiredPermissions, grantedPermissions); - expect(result).to.be.false; - }); - }); - - describe('test with multiple permissions', () => { - it('should throw an error when given an array containing an invalid permission string', async () => { - const requiredPermissions = ['CHANGEOWNER', 'INVALIDPERMISSION']; - const grantedPermissions = - '0x000000000000000000000000000000000000000000000000000000000000ff51'; - expect(() => - checkPermissions(requiredPermissions, grantedPermissions), - ).to.throw( - 'Invalid permission string. It must be a valid 32-byte hex string or a known permission name.', - ); - }); - - it('should throw an error when given an array containing an invalid 32-byte hex string', async () => { - const requiredPermissions = ['CHANGEOWNER', '0xinvalidhexstring']; - const grantedPermissions = - '0x000000000000000000000000000000000000000000000000000000000000ff51'; - expect(() => - checkPermissions(requiredPermissions, grantedPermissions), - ).to.throw( - 'Invalid permission string. It must be a valid 32-byte hex string or a known permission name.', - ); - }); - - it('should return false when one of the literal permissions does not match granted permissions', async () => { - const requiredPermissions = ['EDITPERMISSIONS', 'CALL']; - const grantedPermissions = - '0x000000000000000000000000000000000000000000000000000000000000ff51'; - const result = checkPermissions(requiredPermissions, grantedPermissions); - expect(result).to.be.false; - }); - - it('should return false when one of the bytes32 permissions does not match granted permissions', async () => { - const requiredPermissions = [ - '0x0000000000000000000000000000000000000000000000000000000000000004', - '0x0000000000000000000000000000000000000000000000000000000000000800', - ]; - const grantedPermissions = - '0x000000000000000000000000000000000000000000000000000000000000ff51'; - const result = checkPermissions(requiredPermissions, grantedPermissions); - expect(result).to.be.false; - }); - - it('should return true when all the mixed literal and bytes32 permissions match granted permissions', async () => { - const requiredPermissions = [ - 'EDITPERMISSIONS', - '0x0000000000000000000000000000000000000000000000000000000000000800', - ]; - const grantedPermissions = - '0x000000000000000000000000000000000000000000000000000000000000ff54'; - const result = checkPermissions(requiredPermissions, grantedPermissions); - expect(result).to.be.true; - }); - - it('should return false when not all multiple literal permissions match granted permissions', async () => { - const requiredPermissions = ['CHANGEOWNER', 'CALL']; - const grantedPermissions = - '0x0000000000000000000000000000000000000000000000000000000000000051'; - const result = checkPermissions(requiredPermissions, grantedPermissions); - expect(result).to.be.false; - }); - - it('should return true when all multiple literal permissions match granted permissions', async () => { - const requiredPermissions = ['CHANGEOWNER', 'CALL']; - const grantedPermissions = - '0x0000000000000000000000000000000000000000000000000000000000000801'; - const result = checkPermissions(requiredPermissions, grantedPermissions); - expect(result).to.be.true; - }); - - it('should return false when not all multiple bytes32 permissions match granted permissions', async () => { - const requiredPermissions = [ - '0x0000000000000000000000000000000000000000000000000000000000000001', - '0x0000000000000000000000000000000000000000000000000000000000000800', - ]; - const grantedPermissions = - '0x0000000000000000000000000000000000000000000000000000000000000051'; - const result = checkPermissions(requiredPermissions, grantedPermissions); - expect(result).to.be.false; - }); - - it('should return false when not all mixed literal and bytes32 permissions match granted permissions', async () => { - const requiredPermissions = [ - 'CHANGEOWNER', - '0x0000000000000000000000000000000000000000000000000000000000000800', - ]; - const grantedPermissions = - '0x0000000000000000000000000000000000000000000000000000000000000051'; - const result = checkPermissions(requiredPermissions, grantedPermissions); - expect(result).to.be.false; - }); - }); -}); diff --git a/src/lib/permissions.test.ts b/src/lib/permissions.test.ts new file mode 100644 index 00000000..e7ac432b --- /dev/null +++ b/src/lib/permissions.test.ts @@ -0,0 +1,164 @@ +import { expect } from 'chai'; +import { checkPermissions } from './permissions'; + +describe('checkPermissions', () => { + describe('test with single permission', () => { + it('should throw an error when given an invalid permission string', async () => { + const requiredPermissions = 'INVALIDPERMISSION'; + const grantedPermissions = + '0x000000000000000000000000000000000000000000000000000000000000ff51'; + expect(() => + checkPermissions(requiredPermissions, grantedPermissions), + ).to.throw( + 'Invalid permission string. It must be a valid 32-byte hex string or a known permission name.', + ); + }); + + it('should throw an error when given an invalid 32-byte hex string', async () => { + const requiredPermissions = '0xinvalidhexstring'; + const grantedPermissions = + '0x000000000000000000000000000000000000000000000000000000000000ff51'; + expect(() => + checkPermissions(requiredPermissions, grantedPermissions), + ).to.throw( + 'Invalid permission string. It must be a valid 32-byte hex string or a known permission name.', + ); + }); + + it('should throw an error when given an invalid grantedPermission 32-byte hex string', async () => { + const requiredPermissions = 'CHANGEOWNER'; + const grantedPermissions = '0xinvalidgrantedpermissionhexstring'; + expect(() => + checkPermissions(requiredPermissions, grantedPermissions), + ).to.throw( + 'Invalid grantedPermissions string. It must be a valid 32-byte hex string.', + ); + }); + + it('should return true when single literal permission matches granted permissions', async () => { + const requiredPermissions = 'CHANGEOWNER'; + const grantedPermissions = + '0x000000000000000000000000000000000000000000000000000000000000ff51'; + const result = checkPermissions(requiredPermissions, grantedPermissions); + expect(result).to.be.true; + }); + + it('should return true when single bytes32 permission matches granted permissions', async () => { + const requiredPermissions = + '0x0000000000000000000000000000000000000000000000000000000000000001'; + const grantedPermissions = + '0x000000000000000000000000000000000000000000000000000000000000ff51'; + const result = checkPermissions(requiredPermissions, grantedPermissions); + expect(result).to.be.true; + }); + + it('should return false when single bytes32 permission does not match granted permissions', async () => { + const requiredPermissions = + '0x0000000000000000000000000000000000000000000000000000000000000001'; + const grantedPermissions = + '0x000000000000000000000000000000000000000000000000000000000000fff2'; + const result = checkPermissions(requiredPermissions, grantedPermissions); + expect(result).to.be.false; + }); + + it('should return false when single literal permission does not match granted permissions', async () => { + const requiredPermissions = 'CHANGEOWNER'; + const grantedPermissions = + '0x000000000000000000000000000000000000000000000000000000000000fff2'; + const result = checkPermissions(requiredPermissions, grantedPermissions); + expect(result).to.be.false; + }); + }); + + describe('test with multiple permissions', () => { + it('should throw an error when given an array containing an invalid permission string', async () => { + const requiredPermissions = ['CHANGEOWNER', 'INVALIDPERMISSION']; + const grantedPermissions = + '0x000000000000000000000000000000000000000000000000000000000000ff51'; + expect(() => + checkPermissions(requiredPermissions, grantedPermissions), + ).to.throw( + 'Invalid permission string. It must be a valid 32-byte hex string or a known permission name.', + ); + }); + + it('should throw an error when given an array containing an invalid 32-byte hex string', async () => { + const requiredPermissions = ['CHANGEOWNER', '0xinvalidhexstring']; + const grantedPermissions = + '0x000000000000000000000000000000000000000000000000000000000000ff51'; + expect(() => + checkPermissions(requiredPermissions, grantedPermissions), + ).to.throw( + 'Invalid permission string. It must be a valid 32-byte hex string or a known permission name.', + ); + }); + + it('should return false when one of the literal permissions does not match granted permissions', async () => { + const requiredPermissions = ['EDITPERMISSIONS', 'CALL']; + const grantedPermissions = + '0x000000000000000000000000000000000000000000000000000000000000ff51'; + const result = checkPermissions(requiredPermissions, grantedPermissions); + expect(result).to.be.false; + }); + + it('should return false when one of the bytes32 permissions does not match granted permissions', async () => { + const requiredPermissions = [ + '0x0000000000000000000000000000000000000000000000000000000000000004', + '0x0000000000000000000000000000000000000000000000000000000000000800', + ]; + const grantedPermissions = + '0x000000000000000000000000000000000000000000000000000000000000ff51'; + const result = checkPermissions(requiredPermissions, grantedPermissions); + expect(result).to.be.false; + }); + + it('should return true when all the mixed literal and bytes32 permissions match granted permissions', async () => { + const requiredPermissions = [ + 'EDITPERMISSIONS', + '0x0000000000000000000000000000000000000000000000000000000000000800', + ]; + const grantedPermissions = + '0x000000000000000000000000000000000000000000000000000000000000ff54'; + const result = checkPermissions(requiredPermissions, grantedPermissions); + expect(result).to.be.true; + }); + + it('should return false when not all multiple literal permissions match granted permissions', async () => { + const requiredPermissions = ['CHANGEOWNER', 'CALL']; + const grantedPermissions = + '0x0000000000000000000000000000000000000000000000000000000000000051'; + const result = checkPermissions(requiredPermissions, grantedPermissions); + expect(result).to.be.false; + }); + + it('should return true when all multiple literal permissions match granted permissions', async () => { + const requiredPermissions = ['CHANGEOWNER', 'CALL']; + const grantedPermissions = + '0x0000000000000000000000000000000000000000000000000000000000000801'; + const result = checkPermissions(requiredPermissions, grantedPermissions); + expect(result).to.be.true; + }); + + it('should return false when not all multiple bytes32 permissions match granted permissions', async () => { + const requiredPermissions = [ + '0x0000000000000000000000000000000000000000000000000000000000000001', + '0x0000000000000000000000000000000000000000000000000000000000000800', + ]; + const grantedPermissions = + '0x0000000000000000000000000000000000000000000000000000000000000051'; + const result = checkPermissions(requiredPermissions, grantedPermissions); + expect(result).to.be.false; + }); + + it('should return false when not all mixed literal and bytes32 permissions match granted permissions', async () => { + const requiredPermissions = [ + 'CHANGEOWNER', + '0x0000000000000000000000000000000000000000000000000000000000000800', + ]; + const grantedPermissions = + '0x0000000000000000000000000000000000000000000000000000000000000051'; + const result = checkPermissions(requiredPermissions, grantedPermissions); + expect(result).to.be.false; + }); + }); +}); From d0ee0f7f3804e35f5252ec5c12a908f39c8d4589 Mon Sep 17 00:00:00 2001 From: CJ42 Date: Tue, 21 May 2024 15:41:36 +0200 Subject: [PATCH 7/7] test: fix failing tests --- src/lib/permissions.test.ts | 8 ++++---- src/lib/permissions.ts | 28 +++++++++++++++------------- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/src/lib/permissions.test.ts b/src/lib/permissions.test.ts index e7ac432b..f3822f3a 100644 --- a/src/lib/permissions.test.ts +++ b/src/lib/permissions.test.ts @@ -10,7 +10,7 @@ describe('checkPermissions', () => { expect(() => checkPermissions(requiredPermissions, grantedPermissions), ).to.throw( - 'Invalid permission string. It must be a valid 32-byte hex string or a known permission name.', + `Invalid permission string: ${requiredPermissions}. It must be a valid 32-byte hex string or a known permission name.`, ); }); @@ -21,7 +21,7 @@ describe('checkPermissions', () => { expect(() => checkPermissions(requiredPermissions, grantedPermissions), ).to.throw( - 'Invalid permission string. It must be a valid 32-byte hex string or a known permission name.', + `Invalid permission string: ${requiredPermissions}. It must be a valid 32-byte hex string or a known permission name.`, ); }); @@ -78,7 +78,7 @@ describe('checkPermissions', () => { expect(() => checkPermissions(requiredPermissions, grantedPermissions), ).to.throw( - 'Invalid permission string. It must be a valid 32-byte hex string or a known permission name.', + `Invalid permission string: ${requiredPermissions[1]}. It must be a valid 32-byte hex string or a known permission name.`, ); }); @@ -89,7 +89,7 @@ describe('checkPermissions', () => { expect(() => checkPermissions(requiredPermissions, grantedPermissions), ).to.throw( - 'Invalid permission string. It must be a valid 32-byte hex string or a known permission name.', + `Invalid permission string: ${requiredPermissions[1]}. It must be a valid 32-byte hex string or a known permission name.`, ); }); diff --git a/src/lib/permissions.ts b/src/lib/permissions.ts index 2d680119..917791d5 100644 --- a/src/lib/permissions.ts +++ b/src/lib/permissions.ts @@ -122,18 +122,20 @@ export const checkPermissions = ( requiredPermissionArray.map(mapPermission); // Perform the AND operation check for each required permission - return mappedPermissionArray.every((requiredPermission: string | null) => { - if (!requiredPermission) { - throw new Error( - `Invalid permission string: ${requiredPermission}. It must be a valid 32-byte hex string or a known permission name.`, - ); - } - const requiredPermissionBigInt = BigInt(requiredPermission); - const grantedPermissionsBigInt = BigInt(grantedPermissions); + return mappedPermissionArray.every( + (requiredPermission: string | null, index: number) => { + if (!requiredPermission) { + throw new Error( + `Invalid permission string: ${requiredPermissionArray[index]}. It must be a valid 32-byte hex string or a known permission name.`, + ); + } + const requiredPermissionBigInt = BigInt(requiredPermission); + const grantedPermissionsBigInt = BigInt(grantedPermissions); - return ( - (requiredPermissionBigInt & grantedPermissionsBigInt) === - requiredPermissionBigInt - ); - }); + return ( + (requiredPermissionBigInt & grantedPermissionsBigInt) === + requiredPermissionBigInt + ); + }, + ); };