Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/develop' into feat-multi-type-name
Browse files Browse the repository at this point in the history
  • Loading branch information
Hugoo committed Nov 27, 2023
2 parents 3e337ea + 417a4e8 commit 1e7989b
Show file tree
Hide file tree
Showing 14 changed files with 808 additions and 641 deletions.
395 changes: 233 additions & 162 deletions docs/classes/ERC725.md

Large diffs are not rendered by default.

39 changes: 0 additions & 39 deletions src/constants/interfaces.ts

This file was deleted.

39 changes: 0 additions & 39 deletions src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ import {
SUPPORTED_VERIFICATION_METHOD_STRINGS,
} from './constants/constants';
import { decodeKey } from './lib/decodeData';
import { INTERFACE_IDS_0_12_0 } from './constants/interfaces';

const address = '0x0c03fba782b07bcf810deb3b7f0595024a444f4e';

Expand Down Expand Up @@ -1390,44 +1389,6 @@ describe('encodeKeyName', () => {
});
});

describe('supportsInterface', () => {
const erc725Instance = new ERC725([]);

it('is available on instance and class', () => {
assert.typeOf(ERC725.supportsInterface, 'function');
assert.typeOf(erc725Instance.supportsInterface, 'function');
});

const interfaceId = INTERFACE_IDS_0_12_0.LSP1UniversalReceiver;
const rpcUrl = 'https://my.test.provider';
const contractAddress = '0xcafecafecafecafecafecafecafecafecafecafe';

it('should throw when provided address is not an address', async () => {
try {
await ERC725.supportsInterface(interfaceId, {
address: 'notAnAddress',
rpcUrl,
});
} catch (error: any) {
assert.deepStrictEqual(error.message, 'Invalid address');
}
});

it('should throw when rpcUrl is not provided on non instantiated class', async () => {
try {
await ERC725.supportsInterface(interfaceId, {
address: contractAddress,
// @ts-ignore
rpcUrl: undefined,
});
} catch (error: any) {
assert.deepStrictEqual(error.message, 'Missing RPC URL');
}
});

// TODO: add test to test the actual behavior of the function.
});

describe('checkPermissions', () => {
const erc725Instance = new ERC725([]);

Expand Down
110 changes: 50 additions & 60 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ import { decodeData } from './lib/decodeData';
import { getDataFromExternalSources } from './lib/getDataFromExternalSources';
import { DynamicKeyPart, DynamicKeyParts } from './types/dynamicKeys';
import { getData } from './lib/getData';
import { supportsInterface, checkPermissions } from './lib/detector';
import { checkPermissions } from './lib/detector';
import { decodeValueType, encodeValueType } from './lib/encoder';
import { decodeMappingKey } from './lib/decodeMappingKey';

export {
Expand Down Expand Up @@ -129,7 +130,7 @@ export class ERC725 {
}

/**
* To prevent weird behovior from the lib, we must make sure all the schemas are correct before loading them.
* To prevent weird behavior from the lib, we must make sure all the schemas are correct before loading them.
*
* @param schemas
* @returns
Expand Down Expand Up @@ -179,6 +180,21 @@ export class ERC725 {

throw new Error(`Incorrect or unsupported provider ${providerOrRpcUrl}`);
}

private getAddressAndProvider() {
if (!this.options.address || !isAddress(this.options.address)) {
throw new Error('Missing ERC725 contract address.');
}
if (!this.options.provider) {
throw new Error('Missing provider.');
}

return {
address: this.options.address,
provider: this.options.provider,
};
}

/**
* Gets **decoded data** for one, many or all keys of the specified `ERC725` smart-contract.
* When omitting the `keyOrKeys` parameter, it will get all the keys (as per {@link ERC725JSONSchema | ERC725JSONSchema} definition).
Expand Down Expand Up @@ -415,20 +431,6 @@ export class ERC725 {
);
}

private getAddressAndProvider() {
if (!this.options.address || !isAddress(this.options.address)) {
throw new Error('Missing ERC725 contract address.');
}
if (!this.options.provider) {
throw new Error('Missing provider.');
}

return {
address: this.options.address,
provider: this.options.provider,
};
}

/**
* Encode permissions into a hexadecimal string as defined by the LSP6 KeyManager Standard.
*
Expand Down Expand Up @@ -583,50 +585,6 @@ export class ERC725 {
return decodeMappingKey(keyHash, keyNameOrSchema);
}

/**
* Check if the ERC725 object supports
* a certain interface.
*
* @param interfaceIdOrName Interface ID or supported interface name.
* @returns {Promise<boolean>} if interface is supported.
*/
async supportsInterface(interfaceIdOrName: string): Promise<boolean> {
const { address, provider } = this.getAddressAndProvider();

return supportsInterface(interfaceIdOrName, {
address,
provider,
});
}

/**
* Check if a smart contract address
* supports a certain interface.
*
* @param {string} interfaceIdOrName Interface ID or supported interface name.
* @param options Object of address, RPC URL and optional gas.
* @returns {Promise<boolean>} if interface is supported.
*/
static async supportsInterface(
interfaceIdOrName: string,
options: { address: string; rpcUrl: string; gas?: number },
): Promise<boolean> {
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: this.initializeProvider(
options.rpcUrl,
options?.gas ? options?.gas : DEFAULT_GAS_VALUE,
),
});
}

/**
* Check if the required permissions are included in the granted permissions as defined by the LSP6 KeyManager Standard.
*
Expand Down Expand Up @@ -656,6 +614,38 @@ export class ERC725 {
): boolean {
return ERC725.checkPermissions(requiredPermissions, grantedPermissions);
}

/**
* @param type The valueType to encode the value as
* @param value The value to encode
* @returns The encoded value
*/
static encodeValueType(
type: string,
value: string | string[] | number | number[] | boolean | boolean[],
): string {
return encodeValueType(type, value);
}

encodeValueType(
type: string,
value: string | string[] | number | number[] | boolean | boolean[],
): string {
return ERC725.encodeValueType(type, value);
}

/**
* @param type The valueType to decode the value as
* @param data The data to decode
* @returns The decoded value
*/
static decodeValueType(type: string, data: string) {
return decodeValueType(type, data);
}

decodeValueType(type: string, data: string) {
return ERC725.decodeValueType(type, data);
}
}

export default ERC725;
77 changes: 33 additions & 44 deletions src/lib/decodeData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,15 @@
* @author Callum Grindle <@CallumGrindle>
* @date 2023
*/

import { isHexStrict } from 'web3-utils';
import { COMPACT_BYTES_ARRAY_STRING } from '../constants/constants';

import { DecodeDataInput, DecodeDataOutput } from '../types/decodeData';
import { ERC725JSONSchema } from '../types/ERC725JSONSchema';
import {
ALL_VALUE_TYPES,
ERC725JSONSchema,
isValidValueType,
} from '../types/ERC725JSONSchema';
import { isDynamicKeyName } from './encodeKeyName';
import { valueContentEncodingMap, decodeValueType } from './encoder';
import { getSchemaElement } from './getSchemaElement';
Expand All @@ -33,45 +36,39 @@ import { decodeKeyValue, encodeArrayKey } from './utils';
const tupleValueTypesRegex = /bytes(\d+)/;
const valueContentsBytesRegex = /Bytes(\d+)/;

export const isValidTuple = (valueType: string, valueContent: string) => {
if (valueType.length <= 2 && valueContent.length <= 2) {
const isValidTupleDefinition = (tupleContent: string): boolean => {
if (tupleContent.length <= 2) {
return false;
}

if (
valueType[0] !== '(' &&
valueType[valueType.length - 1] !== ')' &&
valueContent[0] !== '(' &&
valueContent[valueContent.length - 1] !== ')'
tupleContent[0] !== '(' &&
tupleContent[tupleContent.length - 1] !== ')'
) {
return false;
}

// At this stage, we can assume the user is trying to use a tuple, let's throw errors instead of returning
// false
return true;
};

let valueTypeToDecode = valueType;
const extractTupleElements = (tupleContent: string): string[] =>
tupleContent.substring(1, tupleContent.length - 1).split(',');

if (valueType.includes(COMPACT_BYTES_ARRAY_STRING)) {
valueTypeToDecode = valueType.replace(COMPACT_BYTES_ARRAY_STRING, '');
export const isValidTuple = (valueType: string, valueContent: string) => {
if (
!isValidTupleDefinition(valueType) ||
!isValidTupleDefinition(valueContent)
) {
return false;
}

const valueTypeParts = valueTypeToDecode
.substring(1, valueTypeToDecode.length - 1)
.split(',');
// At this stage, we can assume the user is trying to use a tuple,
// let's throw errors instead of returning false

const valueContentParts = valueContent
.substring(1, valueContent.length - 1)
.split(',');
// Sanitize the string to keep only the tuple, if we are dealing with `CompactBytesArray`
const valueTypeToDecode = valueType.replace(COMPACT_BYTES_ARRAY_STRING, '');

const tuplesValidValueTypes = [
'bytes2',
'bytes4',
'bytes8',
'bytes16',
'bytes32',
'address',
];
const valueTypeParts = extractTupleElements(valueTypeToDecode);
const valueContentParts = extractTupleElements(valueContent);

if (valueTypeParts.length !== valueContentParts.length) {
throw new Error(
Expand All @@ -80,9 +77,9 @@ export const isValidTuple = (valueType: string, valueContent: string) => {
}

for (let i = 0; i < valueTypeParts.length; i++) {
if (!tuplesValidValueTypes.includes(valueTypeParts[i])) {
if (!isValidValueType(valueTypeParts[i])) {
throw new Error(
`Invalid tuple for valueType: ${valueType} / valueContent: ${valueContent}. Type: ${valueTypeParts[i]} is not valid. Valid types are: ${tuplesValidValueTypes}`,
`Invalid tuple for valueType: ${valueType} / valueContent: ${valueContent}. Type: ${valueTypeParts[i]} is not valid. Valid types are: ${ALL_VALUE_TYPES}`,
);
}

Expand Down Expand Up @@ -139,27 +136,19 @@ export const decodeTupleKeyValue = (
): Array<string> => {
// We assume data has already been validated at this stage

let valueTypeToDecode = valueType;

if (valueType.includes('[CompactBytesArray')) {
valueTypeToDecode = valueType.replace(COMPACT_BYTES_ARRAY_STRING, '');
}
// Sanitize the string to keep only the tuple, if we are dealing with `CompactBytesArray`
const valueTypeToDecode = valueType.replace(COMPACT_BYTES_ARRAY_STRING, '');

const valueTypeParts = valueTypeToDecode
.substring(1, valueTypeToDecode.length - 1)
.split(',');
const valueContentParts = valueContent
.substring(1, valueContent.length - 1)
.split(',');
const valueTypeParts = extractTupleElements(valueTypeToDecode);
const valueContentParts = extractTupleElements(valueContent);

const bytesLengths: number[] = [];

valueTypeParts.forEach((valueTypePart) => {
const regexMatch = valueTypePart.match(tupleValueTypesRegex);

// if we are dealing with `bytesN`
if (regexMatch) {
bytesLengths.push(parseInt(regexMatch[1], 10));
}
if (regexMatch) bytesLengths.push(parseInt(regexMatch[1], 10));

if (valueTypePart === 'address') bytesLengths.push(20);
});
Expand Down
Loading

0 comments on commit 1e7989b

Please sign in to comment.