From e458a05849c17ac601b3299a184b02af443ea444 Mon Sep 17 00:00:00 2001 From: Andreas Richter <708186+richtera@users.noreply.github.com> Date: Fri, 5 Apr 2024 09:45:23 -0400 Subject: [PATCH] fix: Make sure vscode is setup correctly for biome, fix decode/encodePermissions and add tests. --- .gitignore | 1 - .vscode/launch.json | 27 +++++ .vscode/settings.json | 17 +++ package.json | 1 + src/index.test.ts | 76 ++++++++----- src/index.ts | 87 +++++++++------ src/lib/detector.test.ts | 230 +++++++++++++++++++-------------------- src/lib/detector.ts | 2 +- src/lib/permissions.ts | 82 +++++--------- 9 files changed, 288 insertions(+), 235 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json diff --git a/.gitignore b/.gitignore index 522238a6..a9464fdd 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,6 @@ node_modules /build /docs/build docs/html -.vscode/* .nyc_output coverage *.tgz diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..a0bc49fb --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,27 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Test debugging", + "request": "launch", + "runtimeArgs": ["run-script", "test"], + "runtimeExecutable": "npm", + "skipFiles": ["/**"], + "type": "node" + }, + { + "type": "node", + "request": "launch", + "name": "Launch Program", + "skipFiles": ["/**"], + "program": "${workspaceFolder}/examples/src/fetchData.js", + "outFiles": ["${workspaceFolder}/**/*.js"], + "env": { + "GLOBAL_AGENT_HTTP_PROXY": "http://127.0.0.1:9090" + } + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..152e8f26 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,17 @@ +{ + "editor.formatOnSave": true, + "editor.formatOnPaste": true, + "editor.defaultFormatter": "biomejs.biome", + "[typescript]": { + "editor.defaultFormatter": "biomejs.biome" + }, + "typescript.format.enable": false, + "javascript.format.enable": false, + "editor.codeActionsOnSave": { + "quickfix.biome": "explicit", + "source.organizeImports.biome": "explicit" + }, + "[vue]": { + "editor.defaultFormatter": "biomejs.biome" + } +} diff --git a/package.json b/package.json index 7918fb07..9629ec2b 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "build:main": "tsc -p tsconfig.json", "build:module": "tsc -p tsconfig.module.json", "test": "env TS_NODE_COMPILER_OPTIONS='{\"module\": \"commonjs\" }' nyc --reporter=text --reporter=lcov mocha", + "test:debug": "env TS_NODE_COMPILER_OPTIONS='{\"module\": \"commonjs\" }' mocha --verbose", "lint": "biome check .", "lint:fix": "biome check . --apply", "release": "standard-version" diff --git a/src/index.test.ts b/src/index.test.ts index 2e7cabce..953bd0a2 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -27,7 +27,13 @@ import { hexToNumber, leftPad, numberToHex } from 'web3-utils' // examples of schemas to load (for testing) import { LSP1Schema, LSP3Schema, LSP6Schema, LSP12Schema } from './schemas' -import ERC725 from '.' +import ERC725, { + checkPermissions, + decodePermissions, + encodeKeyName, + encodePermissions, + supportsInterface +} from '.' import { EthereumProvider, HttpProvider } from '../test/mockProviders' import { mockSchema } from '../test/mockSchema' import { @@ -1400,7 +1406,7 @@ describe('Running @erc725/erc725.js tests...', () => { const testCase = testCases[i] it(`Encodes ${testCase.hex} permission correctly`, () => { assert.deepStrictEqual( - ERC725.encodePermissions(testCase.permissions), + encodePermissions(testCase.permissions), testCase.hex ) assert.deepStrictEqual( @@ -1412,7 +1418,7 @@ describe('Running @erc725/erc725.js tests...', () => { it('Defaults permissions to false if not passed', () => { assert.deepStrictEqual( - ERC725.encodePermissions({ + encodePermissions({ EDITPERMISSIONS: true, SETDATA: true }), @@ -1485,7 +1491,7 @@ describe('Running @erc725/erc725.js tests...', () => { for (let i = 0; i < numberOfTests; i++) { it(`Randomized test #${i + 1}`, () => { const randomPermissions = generateRandomPermissions() - const encoded = ERC725.encodePermissions(randomPermissions) + const encoded = encodePermissions(randomPermissions) const expectedHex = calculateExpectedHex(randomPermissions) assert.strictEqual( encoded, @@ -1499,7 +1505,7 @@ describe('Running @erc725/erc725.js tests...', () => { describe('all permissions', () => { it('should encode ALL_PERMISSIONS correctly', () => { const permissions = { ALL_PERMISSIONS: true } - const encoded = ERC725.encodePermissions(permissions) + const encoded = encodePermissions(permissions) assert.strictEqual( encoded, @@ -1508,12 +1514,30 @@ describe('Running @erc725/erc725.js tests...', () => { ) }) - it('should not decode ALL_PERMISSIONS', () => { + it('should decode ALL_PERMISSIONS', () => { const permissions = { ALL_PERMISSIONS: true } - const encoded = ERC725.encodePermissions(permissions) + const encoded = encodePermissions(permissions) - const decodedPermissions = ERC725.decodePermissions(encoded) + const decodedPermissions = decodePermissions(encoded) + assert.strictEqual( + decodedPermissions.ALL_PERMISSIONS, + true, + 'Decoded permissions includes ALL_PERMISSIONS' + ) + }) + + it('should not decode CALL or ALL_PERMISSIONS if perms are missing', () => { + const permissions = { ALL_PERMISSIONS: true, CALL: false } + const encoded = encodePermissions(permissions) + + const decodedPermissions = decodePermissions(encoded) + + assert.strictEqual( + decodedPermissions.CALL, + false, + 'Decoded permissions includes CALL' + ) assert.strictEqual( decodedPermissions.ALL_PERMISSIONS, false, @@ -1523,13 +1547,12 @@ describe('Running @erc725/erc725.js tests...', () => { it('should allow editing of permissions', () => { const permissions = { ALL_PERMISSIONS: true } - const encoded = ERC725.encodePermissions(permissions) + const encoded = encodePermissions(permissions) - const decodedPermissions = ERC725.decodePermissions(encoded) + const decodedPermissions = decodePermissions(encoded) decodedPermissions.CALL = false - const reencodePermissions = ERC725.encodePermissions(decodedPermissions) - const redecodedPermissions = - ERC725.decodePermissions(reencodePermissions) + const reencodePermissions = encodePermissions(decodedPermissions) + const redecodedPermissions = decodePermissions(reencodePermissions) assert.strictEqual( redecodedPermissions.CALL, @@ -1543,7 +1566,7 @@ describe('Running @erc725/erc725.js tests...', () => { ALL_PERMISSIONS: true, CHANGEOWNER: true } - const encoded = ERC725.encodePermissions(permissions) + const encoded = encodePermissions(permissions) assert.strictEqual( encoded, LSP6_DEFAULT_PERMISSIONS.ALL_PERMISSIONS, @@ -1556,8 +1579,8 @@ describe('Running @erc725/erc725.js tests...', () => { CHANGEOWNER: false // Explicitly disable CHANGEOWNER } - const encoded = ERC725.encodePermissions(permissions) - const decodedPermissions = ERC725.decodePermissions(encoded) + const encoded = encodePermissions(permissions) + const decodedPermissions = decodePermissions(encoded) // check that the permission is disabled assert.strictEqual( @@ -1573,7 +1596,7 @@ describe('Running @erc725/erc725.js tests...', () => { const testCase = testCases[i] it(`Decodes ${testCase.hex} permission correctly`, () => { assert.deepStrictEqual( - ERC725.decodePermissions(testCase.hex), + decodePermissions(testCase.hex), testCase.permissions ) @@ -1585,7 +1608,7 @@ describe('Running @erc725/erc725.js tests...', () => { } it('Decodes 0xfff...fff admin permission correctly', () => { assert.deepStrictEqual( - ERC725.decodePermissions( + decodePermissions( '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' ), { @@ -1691,7 +1714,7 @@ describe('encodeKeyName', () => { it('is available on instance and class', () => { assert.deepStrictEqual( - ERC725.encodeKeyName('MyKeyName'), + encodeKeyName('MyKeyName'), '0x35e6950bc8d21a1699e58328a3c4066df5803bb0b570d0150cb3819288e764b2' ) assert.deepStrictEqual( @@ -1702,7 +1725,7 @@ describe('encodeKeyName', () => { it('works for dynamic keys', () => { assert.deepStrictEqual( - ERC725.encodeKeyName( + encodeKeyName( 'FavouriteFood:
', '0xa4FBbFe353124E6fa6Bb7f8e088a9269dF552EA2' ), @@ -1722,7 +1745,7 @@ describe('supportsInterface', () => { const erc725Instance = new ERC725([]) it('is available on instance and class', () => { - assert.typeOf(ERC725.supportsInterface, 'function') + assert.typeOf(supportsInterface, 'function') assert.typeOf(erc725Instance.supportsInterface, 'function') }) @@ -1732,7 +1755,7 @@ describe('supportsInterface', () => { it('should throw when provided address is not an address', async () => { try { - await ERC725.supportsInterface(interfaceId, { + await supportsInterface(interfaceId, { address: 'notAnAddress', rpcUrl }) @@ -1743,7 +1766,7 @@ describe('supportsInterface', () => { it('should throw when rpcUrl is not provided on non instantiated class', async () => { try { - await ERC725.supportsInterface(interfaceId, { + await supportsInterface(interfaceId, { address: contractAddress, // @ts-ignore rpcUrl: undefined @@ -1777,7 +1800,7 @@ describe('checkPermissions', () => { }) it('is available on class', () => { - assert.typeOf(ERC725.checkPermissions, 'function') + assert.typeOf(checkPermissions, 'function') const requiredPermissions = [ '0x0000000000000000000000000000000000000000000000000000000000000004', @@ -1786,10 +1809,7 @@ describe('checkPermissions', () => { const grantedPermissions = '0x000000000000000000000000000000000000000000000000000000000000ff51' - const result = ERC725.checkPermissions( - requiredPermissions, - grantedPermissions - ) + const result = checkPermissions(requiredPermissions, grantedPermissions) assert.equal(result, false) }) diff --git a/src/index.ts b/src/index.ts index 33b8ff84..1d0c8065 100644 --- a/src/index.ts +++ b/src/index.ts @@ -40,7 +40,7 @@ import { encodeKeyName, isDynamicKeyName } from './lib/encodeKeyName' import { decodeData } from './lib/decodeData' import { decodeMappingKey } from './lib/decodeMappingKey' -import { checkPermissions, supportsInterface } from './lib/detector' +import { _supportsInterface, checkPermissions } from './lib/detector' import { decodeValueType, encodeValueType } from './lib/encoder' import { getData } from './lib/getData' import { getDataFromExternalSources } from './lib/getDataFromExternalSources' @@ -67,7 +67,8 @@ export { ERC725JSONSchema, ERC725JSONSchemaKeyType, ERC725JSONSchemaValueContent, - ERC725JSONSchemaValueType + ERC725JSONSchemaValueType, + Permissions } export { ERC725Config, KeyValuePair, ProviderTypes } from './types' @@ -82,7 +83,54 @@ export { } from './lib/encoder' export { getDataFromExternalSources } from './lib/getDataFromExternalSources' export { encodePermissions, decodePermissions } from './lib/permissions' -export { supportsInterface, checkPermissions } from './lib/detector' +export { checkPermissions } from './lib/detector' + +// PRIVATE FUNCTION +function initializeProvider(providerOrRpcUrl, gasInfo) { + // do not fail on no-provider + if (!providerOrRpcUrl) return undefined + + // if provider is a string, assume it's a rpcUrl + if (typeof providerOrRpcUrl === 'string') { + return new ProviderWrapper(new HttpProvider(providerOrRpcUrl), gasInfo) + } + + if ( + typeof providerOrRpcUrl.request === 'function' || + typeof providerOrRpcUrl.send === 'function' + ) + return new ProviderWrapper(providerOrRpcUrl, gasInfo) + + throw new Error(`Incorrect or unsupported provider ${providerOrRpcUrl}`) +} + +// PUBLIC FUNCTION +export async function supportsInterface( + interfaceIdOrName: string, + options: { + address: string + rpcUrl: string + gas?: number + provider?: ProviderWrapper + } +): Promise { + if (!isAddress(options.address)) { + throw new Error('Invalid address') + } + if (!options.rpcUrl) { + throw new Error('Missing RPC URL') + } + + return _supportsInterface(interfaceIdOrName, { + address: options.address, + provider: + options.provider || + initializeProvider( + options.rpcUrl, + options?.gas ? options?.gas : DEFAULT_GAS_VALUE + ) + }) +} /** * This package is currently in early stages of development,
use only for testing or experimentation purposes.
@@ -179,21 +227,7 @@ export class ERC725 { } private static initializeProvider(providerOrRpcUrl, gasInfo) { - // do not fail on no-provider - if (!providerOrRpcUrl) return undefined - - // if provider is a string, assume it's a rpcUrl - if (typeof providerOrRpcUrl === 'string') { - return new ProviderWrapper(new HttpProvider(providerOrRpcUrl), gasInfo) - } - - if ( - typeof providerOrRpcUrl.request === 'function' || - typeof providerOrRpcUrl.send === 'function' - ) - return new ProviderWrapper(providerOrRpcUrl, gasInfo) - - throw new Error(`Incorrect or unsupported provider ${providerOrRpcUrl}`) + return initializeProvider(providerOrRpcUrl, gasInfo) } private getAddressAndProvider() { @@ -559,7 +593,7 @@ export class ERC725 { async supportsInterface(interfaceIdOrName: string): Promise { const { address, provider } = this.getAddressAndProvider() - return supportsInterface(interfaceIdOrName, { + return _supportsInterface(interfaceIdOrName, { address, provider }) @@ -577,20 +611,7 @@ export class ERC725 { interfaceIdOrName: string, options: { address: string; rpcUrl: string; gas?: number } ): Promise { - if (!isAddress(options.address)) { - throw new Error('Invalid address') - } - if (!options.rpcUrl) { - throw new Error('Missing RPC URL') - } - - return supportsInterface(interfaceIdOrName, { - address: options.address, - provider: ERC725.initializeProvider( - options.rpcUrl, - options?.gas ? options?.gas : DEFAULT_GAS_VALUE - ) - }) + return supportsInterface(interfaceIdOrName, options) } /** diff --git a/src/lib/detector.test.ts b/src/lib/detector.test.ts index 98dca2b0..ec418d25 100644 --- a/src/lib/detector.test.ts +++ b/src/lib/detector.test.ts @@ -20,208 +20,208 @@ /* eslint-disable no-unused-expressions */ -import { expect } from 'chai' -import * as sinon from 'sinon' -import { INTERFACE_IDS_0_12_0 } from '../constants/interfaces' +import { expect } from 'chai'; +import * as sinon from 'sinon'; +import { INTERFACE_IDS_0_12_0 } from '../constants/interfaces'; -import { checkPermissions, supportsInterface } from './detector' +import { _supportsInterface, checkPermissions } from './detector'; describe('supportsInterface', () => { it('it should return true if the contract supports the interface with name', async () => { - const contractAddress = '0xcafecafecafecafecafecafecafecafecafecafe' - const interfaceName = 'LSP0ERC725Account' + const contractAddress = '0xcafecafecafecafecafecafecafecafecafecafe'; + const interfaceName = 'LSP0ERC725Account'; - const providerStub = { supportsInterface: sinon.stub() } + const providerStub = { supportsInterface: sinon.stub() }; providerStub.supportsInterface .withArgs(contractAddress, INTERFACE_IDS_0_12_0[interfaceName]) - .returns(Promise.resolve(true)) + .returns(Promise.resolve(true)); - const doesSupportInterface = await supportsInterface(interfaceName, { + const doesSupportInterface = await _supportsInterface(interfaceName, { address: contractAddress, - provider: providerStub - }) + provider: providerStub, + }); - expect(doesSupportInterface).to.be.true - }) + expect(doesSupportInterface).to.be.true; + }); it('it should return true if the contract supports the interface with interfaceId', async () => { - const contractAddress = '0xcafecafecafecafecafecafecafecafecafecafe' - const interfaceId = INTERFACE_IDS_0_12_0.LSP1UniversalReceiver + const contractAddress = '0xcafecafecafecafecafecafecafecafecafecafe'; + const interfaceId = INTERFACE_IDS_0_12_0.LSP1UniversalReceiver; - const providerStub = { supportsInterface: sinon.stub() } + const providerStub = { supportsInterface: sinon.stub() }; providerStub.supportsInterface .withArgs(contractAddress, interfaceId) - .returns(Promise.resolve(true)) + .returns(Promise.resolve(true)); - const doesSupportInterface = await supportsInterface(interfaceId, { + const doesSupportInterface = await _supportsInterface(interfaceId, { address: contractAddress, - provider: providerStub - }) + provider: providerStub, + }); - expect(doesSupportInterface).to.be.true - }) -}) + 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 requiredPermissions = 'INVALIDPERMISSION'; const grantedPermissions = - '0x000000000000000000000000000000000000000000000000000000000000ff51' + '0x000000000000000000000000000000000000000000000000000000000000ff51'; expect(() => - checkPermissions(requiredPermissions, grantedPermissions) + 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. 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 requiredPermissions = '0xinvalidhexstring'; const grantedPermissions = - '0x000000000000000000000000000000000000000000000000000000000000ff51' + '0x000000000000000000000000000000000000000000000000000000000000ff51'; expect(() => - checkPermissions(requiredPermissions, grantedPermissions) + 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. 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' + const requiredPermissions = 'CHANGEOWNER'; + const grantedPermissions = '0xinvalidgrantedpermissionhexstring'; expect(() => - checkPermissions(requiredPermissions, grantedPermissions) + checkPermissions(requiredPermissions, grantedPermissions), ).to.throw( - 'Invalid grantedPermissions string. It must be a valid 32-byte hex string.' - ) - }) + '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 requiredPermissions = 'CHANGEOWNER'; const grantedPermissions = - '0x000000000000000000000000000000000000000000000000000000000000ff51' - const result = checkPermissions(requiredPermissions, grantedPermissions) - expect(result).to.be.true - }) + '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' + '0x0000000000000000000000000000000000000000000000000000000000000001'; const grantedPermissions = - '0x000000000000000000000000000000000000000000000000000000000000ff51' - const result = checkPermissions(requiredPermissions, grantedPermissions) - expect(result).to.be.true - }) + '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' + '0x0000000000000000000000000000000000000000000000000000000000000001'; const grantedPermissions = - '0x000000000000000000000000000000000000000000000000000000000000fff2' - const result = checkPermissions(requiredPermissions, grantedPermissions) - expect(result).to.be.false - }) + '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 requiredPermissions = 'CHANGEOWNER'; const grantedPermissions = - '0x000000000000000000000000000000000000000000000000000000000000fff2' - const result = checkPermissions(requiredPermissions, grantedPermissions) - expect(result).to.be.false - }) - }) + '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 requiredPermissions = ['CHANGEOWNER', 'INVALIDPERMISSION']; const grantedPermissions = - '0x000000000000000000000000000000000000000000000000000000000000ff51' + '0x000000000000000000000000000000000000000000000000000000000000ff51'; expect(() => - checkPermissions(requiredPermissions, grantedPermissions) + 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. 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 requiredPermissions = ['CHANGEOWNER', '0xinvalidhexstring']; const grantedPermissions = - '0x000000000000000000000000000000000000000000000000000000000000ff51' + '0x000000000000000000000000000000000000000000000000000000000000ff51'; expect(() => - checkPermissions(requiredPermissions, grantedPermissions) + 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. 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 requiredPermissions = ['EDITPERMISSIONS', 'CALL']; const grantedPermissions = - '0x000000000000000000000000000000000000000000000000000000000000ff51' - const result = checkPermissions(requiredPermissions, grantedPermissions) - expect(result).to.be.false - }) + '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' - ] + '0x0000000000000000000000000000000000000000000000000000000000000800', + ]; const grantedPermissions = - '0x000000000000000000000000000000000000000000000000000000000000ff51' - const result = checkPermissions(requiredPermissions, grantedPermissions) - expect(result).to.be.false - }) + '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' - ] + '0x0000000000000000000000000000000000000000000000000000000000000800', + ]; const grantedPermissions = - '0x000000000000000000000000000000000000000000000000000000000000ff54' - const result = checkPermissions(requiredPermissions, grantedPermissions) - expect(result).to.be.true - }) + '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 requiredPermissions = ['CHANGEOWNER', 'CALL']; const grantedPermissions = - '0x0000000000000000000000000000000000000000000000000000000000000051' - const result = checkPermissions(requiredPermissions, grantedPermissions) - expect(result).to.be.false - }) + '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 requiredPermissions = ['CHANGEOWNER', 'CALL']; const grantedPermissions = - '0x0000000000000000000000000000000000000000000000000000000000000801' - const result = checkPermissions(requiredPermissions, grantedPermissions) - expect(result).to.be.true - }) + '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' - ] + '0x0000000000000000000000000000000000000000000000000000000000000800', + ]; const grantedPermissions = - '0x0000000000000000000000000000000000000000000000000000000000000051' - const result = checkPermissions(requiredPermissions, grantedPermissions) - expect(result).to.be.false - }) + '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' - ] + '0x0000000000000000000000000000000000000000000000000000000000000800', + ]; const grantedPermissions = - '0x0000000000000000000000000000000000000000000000000000000000000051' - const result = checkPermissions(requiredPermissions, grantedPermissions) - expect(result).to.be.false - }) - }) -}) + '0x0000000000000000000000000000000000000000000000000000000000000051'; + const result = checkPermissions(requiredPermissions, grantedPermissions); + expect(result).to.be.false; + }); + }); +}); diff --git a/src/lib/detector.ts b/src/lib/detector.ts index aef8eb73..7225aa8a 100644 --- a/src/lib/detector.ts +++ b/src/lib/detector.ts @@ -36,7 +36,7 @@ import { * @param options Object with address and RPC URL. * @returns {Promise} if interface is supported. */ -export const supportsInterface = async ( +export const _supportsInterface = async ( interfaceIdOrName: string, options: AddressProviderOptions ): Promise => { diff --git a/src/lib/permissions.ts b/src/lib/permissions.ts index 19209603..c4af39c5 100644 --- a/src/lib/permissions.ts +++ b/src/lib/permissions.ts @@ -1,56 +1,33 @@ import { hexToNumber, leftPad, toHex } from 'web3-utils' -import { - LSP6_ALL_PERMISSIONS, - LSP6_DEFAULT_PERMISSIONS -} from '../constants/constants' +import { LSP6_DEFAULT_PERMISSIONS } from '../constants/constants' import { Permissions } from '../types/Method' export function encodePermissions(permissions: Permissions): string { let basePermissions = BigInt(0) - // If ALL_PERMISSIONS is requested, start with that as the base - if (permissions.ALL_PERMISSIONS) { - basePermissions = BigInt( - hexToNumber(LSP6_DEFAULT_PERMISSIONS.ALL_PERMISSIONS) - ) - } - - // Explicitly add REENTRANCY, DELEGATECALL, and SUPER_DELEGATECALL if requested (they are not included in ALL_PERMISSIONS) - const additionalPermissions = [ - LSP6_DEFAULT_PERMISSIONS.REENTRANCY, - LSP6_DEFAULT_PERMISSIONS.DELEGATECALL, - LSP6_DEFAULT_PERMISSIONS.SUPER_DELEGATECALL - ] - // Do not use an for of loop here to not require the regenerator runtime - for (let i = 0; i < additionalPermissions.length; i += 1) { - const permission = additionalPermissions[i] - if (permissions[permission]) { - basePermissions |= BigInt( - hexToNumber(LSP6_DEFAULT_PERMISSIONS[permission]) - ) - } - } - // Process each permission to potentially switch off permissions included in ALL_PERMISSIONS - const keys = Object.keys(permissions) + // Deal with ALL_PERMISSIONS first IMPORTANT! + const keys = Object.keys(permissions).filter( + (key) => key !== 'ALL_PERMISSIONS' + ) + keys.splice(0, 0, 'ALL_PERMISSIONS') // Do not use an for of loop here to not require the regenerator runtime for (let i = 0; i < keys.length; i += 1) { const key = keys[i] - const permissionValue = BigInt(hexToNumber(LSP6_DEFAULT_PERMISSIONS[key])) - + const permissionValue = BigInt( + hexToNumber(LSP6_DEFAULT_PERMISSIONS[key], true) + ) + console.log( + 'update', + key, + permissions[key], + permissionValue, + basePermissions + ) if (permissions[key]) { - // If not dealing with ALL_PERMISSIONS or additional permissions, ensure they are added - if ( - !additionalPermissions.includes(key) && - key !== LSP6_DEFAULT_PERMISSIONS.ALL_PERMISSIONS - ) { - basePermissions |= permissionValue - } - } else if ( - LSP6_DEFAULT_PERMISSIONS[key] !== LSP6_DEFAULT_PERMISSIONS.ALL_PERMISSIONS - ) { - // If permission is set to false, remove it from the basePermissions - basePermissions &= ~permissionValue + basePermissions |= permissionValue + } else { + basePermissions = basePermissions & ~permissionValue } } // Convert the final BigInt permission value back to a hex string, properly padded @@ -86,26 +63,17 @@ export function decodePermissions(permissionHex: string) { ALL_PERMISSIONS: false } - // const permissionsToTest = Object.keys(LSP6_DEFAULT_PERMISSIONS).filter( - // (key) => key !== 'ALL_PERMISSIONS', - // ); const permissionsToTest = Object.keys(LSP6_DEFAULT_PERMISSIONS) - if (permissionHex === LSP6_ALL_PERMISSIONS) { - // Do not use an for of loop here to not require the regenerator runtime - for (let i = 0; i < permissionsToTest.length; i += 1) { - const testPermission = permissionsToTest[i] - result[testPermission] = true - } - return result - } - - const passedPermissionDecimal = Number(hexToNumber(permissionHex)) + const passedPermissionDecimal = BigInt(hexToNumber(permissionHex, true)) // Do not use an for of loop here to not require the regenerator runtime + // Deal with ALL_PERMISSIONS the same way. So as long as all the bits in ALL_PERMISSIONS + // are set the same way as in ALL_PERMISSIONS then this flag will return as true. + // It does not mean some extra permissions are not included. for (let i = 0; i < permissionsToTest.length; i += 1) { const testPermission = permissionsToTest[i] - const decimalTestPermission = Number( - hexToNumber(LSP6_DEFAULT_PERMISSIONS[testPermission]) + const decimalTestPermission = BigInt( + hexToNumber(LSP6_DEFAULT_PERMISSIONS[testPermission], true) ) const isPermissionIncluded = (passedPermissionDecimal & decimalTestPermission) ===