From a6fe7c8470688f573426b59fc2023a08da0cbd36 Mon Sep 17 00:00:00 2001
From: Jean Cvllr <31145285+CJ42@users.noreply.github.com>
Date: Fri, 24 Nov 2023 15:03:31 +0000
Subject: [PATCH 1/2] feat: add `encode/decodeValueType` as public callable
methods (#325)
* feat: add `encode/decodeValueType` as public callable methods
* docs: improve parameter names + add examples in documentation
---
docs/classes/ERC725.md | 289 +++++++++++++++++++++++++++++------------
src/index.ts | 62 +++++++--
src/lib/encoder.ts | 10 +-
3 files changed, 262 insertions(+), 99 deletions(-)
diff --git a/docs/classes/ERC725.md b/docs/classes/ERC725.md
index d50f7c76..269870ce 100644
--- a/docs/classes/ERC725.md
+++ b/docs/classes/ERC725.md
@@ -342,6 +342,67 @@ myErc725.decodePermissions('0x00000000000000000000000000000000000000000000000000
---
+## decodeValueType
+
+```js
+myErc725.decodeValueType(type, data);
+```
+
+```js
+ERC725.decodeValueType(type, data);
+```
+
+Decode some data according to a provided value type.
+
+#### Parameters
+
+| Name | Type | Description |
+| :----- | :----- | :---------------------------------------------------------------------------- |
+| `type` | string | The value type to decode the data (i.e. `uint256`, `bool`, `bytes4`, etc...). |
+| `data` | string | A hex encoded string starting with `0x` to decode |
+
+#### Returns
+
+| Name | Type | Description |
+| :------------- | :--------------------- | :----------------------------------- |
+| `decodedValue` | string or
number | A value decoded according to `type`. |
+
+#### Examples
+
+```javascript
+myErc725.decodeValueType('uint128', '0x0000000000000000000000000000000a');
+// 10
+
+myErc725.decodeValueType('bool', '0x01');
+// true
+
+myErc725.decodeValueType('string', '0x48656c6c6f21');
+// 'Hello!';
+
+// also available for ABI encoded array + CompactBytesArray
+myErc725.decodeValueType(
+ 'uint256[]',
+ '0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000001e',
+);
+// [ 10, 20, 30 ]
+
+myErc725.decodeValueType(
+ 'uint256[CompactBytesArray]'',
+ '0x0020000000000000000000000000000000000000000000000000000000000000000500200000000000000000000000000000000000000000000000000000000000000008'
+)
+// [ 5, 8 ]
+```
+
+This method is also available as a static method:
+
+```js
+ERC725.decodeValueType(
+ 'uint256',
+ '0x000000000000000000000000000000000000000000000000000000000000002a',
+);
+// 42
+```
+
## encodeData
```js
@@ -617,6 +678,154 @@ myErc725.encodeData([
---
+## encodePermissions
+
+```js
+ERC725.encodePermissions(permissions);
+```
+
+Encodes permissions into a hexadecimal string as defined by the [LSP6 KeyManager Standard](https://docs.lukso.tech/standards/universal-profile/lsp6-key-manager).
+
+:::info
+
+`encodePermissions` is available as either a static or non-static method so can be called without instantiating an ERC725 object.
+
+:::
+
+#### Parameters
+
+##### 1. `permissions` - Object
+
+An object with [LSP6 KeyManager Permissions] as keys and a `boolean` as value. Any ommited permissions will default to `false`.
+
+#### Returns
+
+| Type | Description |
+| :----- | :---------------------------------------------------------------------------------------- |
+| string | The permissions encoded as a hexadecimal string defined by the [LSP6 KeyManager Standard] |
+
+#### Example
+
+```javascript title="Encoding permissions"
+ERC725.encodePermissions({
+ CHANGEOWNER: false,
+ ADDCONTROLLER: false,
+ EDITPERMISSIONS: false,
+ ADDEXTENSIONS: false,
+ CHANGEEXTENSIONS: true,
+ ADDUNIVERSALRECEIVERDELEGATE: false,
+ CHANGEUNIVERSALRECEIVERDELEGATE: false,
+ REENTRANCY: false,
+ SUPER_TRANSFERVALUE: true,
+ TRANSFERVALUE: true,
+ SUPER_CALL: false,
+ CALL: true,
+ SUPER_STATICCALL: false,
+ STATICCALL: false,
+ SUPER_DELEGATECALL: false,
+ DELEGATECALL: false,
+ DEPLOY: false,
+ SUPER_SETDATA: false,
+ SETDATA: false,
+ ENCRYPT: false,
+ DECRYPT: false,
+ SIGN: false,
+ EXECUTE_RELAY_CALL: false
+}),
+// '0x0000000000000000000000000000000000000000000000000000000000000110'
+
+// Any ommited Permissions will default to false
+ERC725.encodePermissions({
+ ADDCONTROLLER: true,
+ ADDEXTENSIONS: true,
+}),
+// '0x000000000000000000000000000000000000000000000000000000000000000a'
+ERC725.encodePermissions({
+ EDITPERMISSIONS: true,
+ CHANGEEXTENSIONS: true,
+ CHANGEUNIVERSALRECEIVERDELEGATE: true,
+ SETDATA: true,
+}),
+// '0x0000000000000000000000000000000000000000000000000000000000040054'
+
+
+// This method is also available on the instance:
+myErc725.encodePermissions({
+ EDITPERMISSIONS: true,
+ SETDATA: true,
+}),
+```
+
+---
+
+## encodeValueType
+
+```js
+myErc725.encodeValueType(type, value);
+```
+
+```js
+ERC725.encodeValueType(type, value);
+```
+
+#### Parameters
+
+| Name | Type | Description |
+| :------ | :--------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------- |
+| `type` | string | The value type to encode the value (i.e. `uint256`, `bool`, `bytes4`, etc...). |
+| `value` | string or
string[ ] or
number or
number[ ] or
boolean or
boolean[] | The value that should be encoded as `type` |
+
+#### Returns
+
+| Name | Type | Description |
+| :----------------- | :----- | :------------------------------------------------------- |
+| `encodedValueType` | string | A hex string representing the `value` encoded as `type`. |
+
+After the `value` is encoded, the hex string can be used to be stored inside the ERC725Y smart contract.
+
+#### Examples
+
+```javascript
+myErc725.encodeValueType('uint256', 5);
+// '0x0000000000000000000000000000000000000000000000000000000000000005'
+
+myErc725.encodeValueType('bool', true);
+// '0x01'
+
+// the word `boolean` (Name of the Typescript type) is also available
+myErc725.encodeValueType('boolean', true);
+// '0x01'
+
+// `bytesN` type will pad on the right if the value contains less than N bytes
+myErc725.encodeValueType('bytes4', '0xcafe');
+// '0xcafe0000'
+myErc725.encodeValueType('bytes32', '0xcafe');
+// '0xcafe000000000000000000000000000000000000000000000000000000000000'
+
+// `bytesN` type will throw an error if the value contains more than N bytes
+myERC725.encodeValueType('bytes4', '0xcafecafebeef');
+// Error: Can't convert 0xcafecafebeef to bytes4. Too many bytes, expected at most 4 bytes, received 6.
+
+// Can also be used to encode arrays as `CompactBytesArray`
+myERC725.encodeValueType('uint256[CompactBytesArray]', [1, 2, 3]);
+// '0x002000000000000000000000000000000000000000000000000000000000000000010020000000000000000000000000000000000000000000000000000000000000000200200000000000000000000000000000000000000000000000000000000000000003'
+
+myERC725.encodeValueType('bytes[CompactBytesArray]', [
+ '0xaaaaaaaa',
+ '0xbbbbbbbbbbbbbbbbbb',
+]);
+// '0x0004aaaaaaaa0009bbbbbbbbbbbbbbbbbb'
+```
+
+This method is also available as a static method.
+
+```javascript
+ERC725.encodeValueType('string', 'Hello');
+// '0x48656c6c6f'
+```
+
+---
+
## encodeKeyName
```js
@@ -766,86 +975,6 @@ myErc725.decodeMappingKey(
---
-## encodePermissions
-
-```js
-ERC725.encodePermissions(permissions);
-```
-
-Encodes permissions into a hexadecimal string as defined by the [LSP6 KeyManager Standard](https://docs.lukso.tech/standards/universal-profile/lsp6-key-manager).
-
-:::info
-
-`encodePermissions` is available as either a static or non-static method so can be called without instantiating an ERC725 object.
-
-:::
-
-#### Parameters
-
-##### 1. `permissions` - Object
-
-An object with [LSP6 KeyManager Permissions] as keys and a `boolean` as value. Any ommited permissions will default to `false`.
-
-#### Returns
-
-| Type | Description |
-| :----- | :---------------------------------------------------------------------------------------- |
-| string | The permissions encoded as a hexadecimal string defined by the [LSP6 KeyManager Standard] |
-
-#### Example
-
-```javascript title="Encoding permissions"
-ERC725.encodePermissions({
- CHANGEOWNER: false,
- ADDCONTROLLER: false,
- EDITPERMISSIONS: false,
- ADDEXTENSIONS: false,
- CHANGEEXTENSIONS: true,
- ADDUNIVERSALRECEIVERDELEGATE: false,
- CHANGEUNIVERSALRECEIVERDELEGATE: false,
- REENTRANCY: false,
- SUPER_TRANSFERVALUE: true,
- TRANSFERVALUE: true,
- SUPER_CALL: false,
- CALL: true,
- SUPER_STATICCALL: false,
- STATICCALL: false,
- SUPER_DELEGATECALL: false,
- DELEGATECALL: false,
- DEPLOY: false,
- SUPER_SETDATA: false,
- SETDATA: false,
- ENCRYPT: false,
- DECRYPT: false,
- SIGN: false,
- EXECUTE_RELAY_CALL: false
-}),
-// '0x0000000000000000000000000000000000000000000000000000000000000110'
-
-// Any ommited Permissions will default to false
-ERC725.encodePermissions({
- ADDCONTROLLER: true,
- ADDEXTENSIONS: true,
-}),
-// '0x000000000000000000000000000000000000000000000000000000000000000a'
-ERC725.encodePermissions({
- EDITPERMISSIONS: true,
- CHANGEEXTENSIONS: true,
- CHANGEUNIVERSALRECEIVERDELEGATE: true,
- SETDATA: true,
-}),
-// '0x0000000000000000000000000000000000000000000000000000000000040054'
-
-
-// This method is also available on the instance:
-myErc725.encodePermissions({
- EDITPERMISSIONS: true,
- SETDATA: true,
-}),
-```
-
----
-
## fetchData
```js
diff --git a/src/index.ts b/src/index.ts
index 6cf27f14..957d5c50 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -62,6 +62,7 @@ import { getDataFromExternalSources } from './lib/getDataFromExternalSources';
import { DynamicKeyPart, DynamicKeyParts } from './types/dynamicKeys';
import { getData } from './lib/getData';
import { checkPermissions } from './lib/detector';
+import { decodeValueType, encodeValueType } from './lib/encoder';
import { decodeMappingKey } from './lib/decodeMappingKey';
export {
@@ -174,6 +175,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).
@@ -410,20 +426,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.
*
@@ -607,6 +609,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;
diff --git a/src/lib/encoder.ts b/src/lib/encoder.ts
index 6ff989b3..42f93890 100644
--- a/src/lib/encoder.ts
+++ b/src/lib/encoder.ts
@@ -740,18 +740,18 @@ export function encodeValueType(
return valueTypeEncodingMap[type].encode(value);
}
-export function decodeValueType(type: string, value: string) {
+export function decodeValueType(type: string, data: string) {
if (!valueTypeEncodingMap[type]) {
throw new Error('Could not decode valueType: "' + type + '".');
}
- if (value === '0x') return null;
+ if (data === '0x') return null;
- if (typeof value === 'undefined' || value === null) {
- return value;
+ if (typeof data === 'undefined' || data === null) {
+ return data;
}
- return valueTypeEncodingMap[type].decode(value);
+ return valueTypeEncodingMap[type].decode(data);
}
export function encodeValueContent(
From 3a6be551d889904b7d95e2630ab637f2a31feb50 Mon Sep 17 00:00:00 2001
From: Jean Cvllr <31145285+CJ42@users.noreply.github.com>
Date: Fri, 24 Nov 2023 15:04:09 +0000
Subject: [PATCH 2/2] feat: allow to encode LSP2 Array length only (#326)
* feat(wip): allow to encode LSP2 Array length only
* refactor: add typing `number` for `EncodeDataType`
* docs: add docs for encode array length
---------
Co-authored-by: Hugo Masclet
---
docs/classes/ERC725.md | 24 +++++++++++++
src/index.ts | 2 +-
src/lib/encoder.ts | 37 ++++++-------------
src/lib/utils.test.ts | 63 ++++++++++++++++++++++++++-------
src/lib/utils.ts | 5 +++
src/types/encodeData/JSONURL.ts | 7 +++-
6 files changed, 97 insertions(+), 41 deletions(-)
diff --git a/docs/classes/ERC725.md b/docs/classes/ERC725.md
index 269870ce..2fceb610 100644
--- a/docs/classes/ERC725.md
+++ b/docs/classes/ERC725.md
@@ -676,6 +676,30 @@ myErc725.encodeData([
+
+ Encode array length
+
+If the key is of type Array and you pass an integer as a value (for instance, the array length), it will be encoded accordingly.
+
+```javascript title="Encode the length of an array"
+myErc725.encodeData([
+ {
+ keyName: 'LSP3IssuedAssets[]',
+ value: 5,
+ },
+]);
+/**
+{
+ keys: [
+ '0x3a47ab5bd3a594c3a8995f8fa58d0876c96819ca4516bd76100c92462f2f9dc0',
+ ],
+ values: ['0x00000000000000000000000000000005'],
+}
+*/
+```
+
+
+
---
## encodePermissions
diff --git a/src/index.ts b/src/index.ts
index 957d5c50..7287a2e7 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -125,7 +125,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
diff --git a/src/lib/encoder.ts b/src/lib/encoder.ts
index 42f93890..0944edd6 100644
--- a/src/lib/encoder.ts
+++ b/src/lib/encoder.ts
@@ -332,30 +332,6 @@ const returnTypesOfUintNCompactBytesArray = () => {
return types;
};
-/**
- * Encodes any set of strings to string[CompactBytesArray]
- *
- * @param values An array of non restricted strings
- * @returns string[CompactBytesArray]
- */
-const encodeStringCompactBytesArray = (values: string[]): string => {
- const hexValues: string[] = values.map((element) => utf8ToHex(element));
-
- return encodeCompactBytesArray(hexValues);
-};
-
-/**
- * Decode a string[CompactBytesArray] to an array of strings
- * @param compactBytesArray A string[CompactBytesArray]
- * @returns An array of strings
- */
-const decodeStringCompactBytesArray = (compactBytesArray: string): string[] => {
- const hexValues: string[] = decodeCompactBytesArray(compactBytesArray);
- const stringValues: string[] = hexValues.map((element) => hexToUtf8(element));
-
- return stringValues;
-};
-
const valueTypeEncodingMap = {
bool: {
encode: (value: boolean) => (value ? '0x01' : '0x00'),
@@ -494,8 +470,17 @@ const valueTypeEncodingMap = {
decode: (value: string) => decodeCompactBytesArray(value),
},
'string[CompactBytesArray]': {
- encode: (value: string[]) => encodeStringCompactBytesArray(value),
- decode: (value: string) => decodeStringCompactBytesArray(value),
+ encode: (values: string[]) => {
+ const hexValues: string[] = values.map((element) => utf8ToHex(element));
+ return encodeCompactBytesArray(hexValues);
+ },
+ decode: (value: string) => {
+ const hexValues: string[] = decodeCompactBytesArray(value);
+ const stringValues: string[] = hexValues.map((element) =>
+ hexToUtf8(element),
+ );
+ return stringValues;
+ },
},
...returnTypesOfBytesNCompactBytesArray(),
...returnTypesOfUintNCompactBytesArray(),
diff --git a/src/lib/utils.test.ts b/src/lib/utils.test.ts
index 0ae455ad..57a5add0 100644
--- a/src/lib/utils.test.ts
+++ b/src/lib/utils.test.ts
@@ -29,7 +29,6 @@ import { SUPPORTED_VERIFICATION_METHOD_STRINGS } from '../constants/constants';
import {
guessKeyTypeFromKeyName,
isDataAuthentic,
- encodeArrayKey,
encodeKeyValue,
decodeKeyValue,
encodeKey,
@@ -44,6 +43,7 @@ import { decodeKey } from './decodeData';
describe('utils', () => {
describe('encodeKey/decodeKey', () => {
const testCases = [
+ // test encoding an array of address
{
schema: {
name: 'LSP3IssuedAssets[]',
@@ -226,12 +226,28 @@ describe('utils', () => {
encodeKey(testCase.schema as ERC725JSONSchema, testCase.decodedValue),
testCase.encodedValue,
);
+
assert.deepStrictEqual(
decodeKey(testCase.schema as ERC725JSONSchema, testCase.encodedValue),
testCase.decodedValue,
);
});
});
+
+ it('should encode the array length only if passing a number', async () => {
+ const schema: ERC725JSONSchema = {
+ name: 'LSP3IssuedAssets[]',
+ key: '0x3a47ab5bd3a594c3a8995f8fa58d0876c96819ca4516bd76100c92462f2f9dc0',
+ keyType: 'Array',
+ valueContent: 'Address',
+ valueType: 'address',
+ };
+
+ const decodedValue = 3;
+ const encodedValue = '0x00000000000000000000000000000003';
+
+ assert.equal(encodeKey(schema, decodedValue), encodedValue);
+ });
});
describe('encodeKeyValue/decodeKeyValue', () => {
@@ -391,19 +407,19 @@ describe('utils', () => {
});
describe('encodeArrayKey', () => {
- it('encodes array key correctly', () => {
- const key =
- '0x3a47ab5bd3a594c3a8995f8fa58d0876c96819ca4516bd76100c92462f2f9dc0';
-
- const expectedValues = [
- '0x3a47ab5bd3a594c3a8995f8fa58d087600000000000000000000000000000000',
- '0x3a47ab5bd3a594c3a8995f8fa58d087600000000000000000000000000000001',
- '0x3a47ab5bd3a594c3a8995f8fa58d087600000000000000000000000000000002',
- ];
+ it('should encode the array length only if passing a number', async () => {
+ const schema: ERC725JSONSchema = {
+ name: 'LSP3IssuedAssets[]',
+ key: '0x3a47ab5bd3a594c3a8995f8fa58d0876c96819ca4516bd76100c92462f2f9dc0',
+ keyType: 'Array',
+ valueContent: 'Address',
+ valueType: 'address',
+ };
- expectedValues.forEach((expectedValue, index) => {
- assert.strictEqual(encodeArrayKey(key, index), expectedValue);
- });
+ const decodedValue = 3;
+ const encodedValue = '0x00000000000000000000000000000003';
+
+ assert.equal(encodeKey(schema, decodedValue), encodedValue);
});
});
@@ -567,6 +583,27 @@ describe('utils', () => {
});
});
+ it('encodes array length only if giving a number', () => {
+ const length = 5;
+
+ const encodedArrayLengthKey = encodeData(
+ [
+ {
+ keyName: 'LSP3IssuedAssets[]',
+ value: length,
+ },
+ ],
+ schemas,
+ );
+
+ assert.deepStrictEqual(encodedArrayLengthKey, {
+ keys: [
+ '0x3a47ab5bd3a594c3a8995f8fa58d0876c96819ca4516bd76100c92462f2f9dc0',
+ ],
+ values: ['0x00000000000000000000000000000005'],
+ });
+ });
+
it('encodes multiple keys', () => {
const encodedMultipleKeys = encodeData(
[
diff --git a/src/lib/utils.ts b/src/lib/utils.ts
index a29a9a72..0e5555c9 100644
--- a/src/lib/utils.ts
+++ b/src/lib/utils.ts
@@ -249,6 +249,11 @@ export function encodeKey(
switch (lowerCaseKeyType) {
case 'array': {
+ // if we are encoding only the Array length
+ if (typeof value === 'number') {
+ return encodeValueType('uint128', value);
+ }
+
if (!Array.isArray(value)) {
console.error("Can't encode a non array for key of type array");
return null;
diff --git a/src/types/encodeData/JSONURL.ts b/src/types/encodeData/JSONURL.ts
index 1c00ac90..4948cdc9 100644
--- a/src/types/encodeData/JSONURL.ts
+++ b/src/types/encodeData/JSONURL.ts
@@ -27,7 +27,12 @@ export interface URLDataWithJson extends URLData {
export type JSONURLDataToEncode = URLDataWithHash | URLDataWithJson;
-export type EncodeDataType = string | string[] | JSONURLDataToEncode | boolean;
+export type EncodeDataType =
+ | string
+ | string[]
+ | JSONURLDataToEncode
+ | boolean
+ | number;
export interface EncodeDataReturn {
keys: string[];