diff --git a/docs/classes/ERC725.md b/docs/classes/ERC725.md
index d26b5c78..b2194955 100644
--- a/docs/classes/ERC725.md
+++ b/docs/classes/ERC725.md
@@ -444,16 +444,36 @@ _Example 2: input `1122334455` encoded as `bytes4` --> will encode as `0x42e576f
An array of objects containing the following properties:
-| Name | Type | Description |
-| :--------------------------- | :--------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `keyName` | string | Can be either the named key (i.e. `LSP3Profile`, `LSP12IssuedAssetsMap:
`) or the hashed key (with or without `0x` prefix, i.e. `0x5ef...` or `5ef...`). |
-| `dynamicKeyParts` (optional) | string or
string[ ] | The dynamic parts of the `keyName` that will be used for encoding the key. |
-| `value` | string or
string[ ]
JSON todo | The value that should be encoded. Can be a string, an array of string or a JSON... |
+| Name | Type | Description |
+| :---------------------------- | :--------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `keyName` | string | Can be either the named key (i.e. `LSP3Profile`, `LSP12IssuedAssetsMap:`) or the hashed key (with or without `0x` prefix, i.e. `0x5ef...` or `5ef...`). |
+| `dynamicKeyParts` (optional) | string or
string[ ] | The dynamic parts of the `keyName` that will be used for encoding the key. |
+| `value` | string or
string[ ]
JSON todo | The value that should be encoded. Can be a string, an array of string or a JSON... |
+| `startingIndex` (optional) | number | Starting index for `Array` types to encode a subset of elements. Defaults t `0`. |
+| `totalArrayLength` (optional) | number | Parameter for `Array` types, specifying the total length when encoding a subset of elements. Defaults to the number of elements in the `value` field. |
The `keyName` also supports dynamic keys for [`Mapping`](https://github.com/lukso-network/LIPs/blob/main/LSPs/LSP-2-ERC725YJSONSchema.md#mapping) and [`MappingWithGrouping`](https://github.com/lukso-network/LIPs/blob/main/LSPs/LSP-2-ERC725YJSONSchema.md#mapping). Therefore, you can use variables in the key name such as `LSP12IssuedAssetsMap:`. In that case, the value should also set the `dynamicKeyParts` property:
- `dynamicKeyParts`: string or string[ ] which holds the variables that needs to be encoded.
+:::info Handling array subsets
+
+The `totalArrayLength` parameter must be explicitly provided to ensure integrity when encoding subsets or modifying existing array elements. Its value specifies the total length of the array **after the operation is completed**, not just the size of the encoded subset.
+
+**When to Use `totalArrayLength`**
+
+- **Adding Elements:** When adding new elements to an array, `totalArrayLength` should equal the sum of the current array's length plus the number of new elements added.
+- **Modifying Elements:** If modifying elements within an existing array without changing the total number of elements, `totalArrayLength` should match the previous length of the array.
+- **Removing Elements:** In cases where elements are removed, `totalArrayLength` should reflect the number of elements left.
+
+:::
+
+:::caution Encoding array lengths
+
+Please be careful when updating existing contract data. Incorrect usage of `startingIndex` and `totalArrayLength` can lead to improperly encoded data that changes the intended structure of the data field.
+
+:::
+
##### 2. `schemas` - Array of Objects (optional)
An array of extra [LSP-2 ERC725YJSONSchema] objects that can be used to find the schema. If called on an instance, it is optional and it will be concatenated with the schema provided on instantiation.
@@ -700,6 +720,52 @@ myErc725.encodeData([
+
+ Encode a subset of array elements
+
+```javascript title="Encode a subset of array elements"
+const schemas = [
+ {
+ name: 'AddressPermissions[]',
+ key: '0xdf30dba06db6a30e65354d9a64c609861f089545ca58c6b4dbe31a5f338cb0e3',
+ keyType: 'Array',
+ valueType: 'address',
+ valueContent: 'Address',
+ },
+];
+
+myErc725.encodeData(
+ [
+ {
+ keyName: 'AddressPermissions[]',
+ value: [
+ '0x983abc616f2442bab7a917e6bb8660df8b01f3bf',
+ '0x56ecbc104136d00eb37aa0dce60e075f10292d81',
+ ],
+ totalArrayLength: 23,
+ startingIndex: 21,
+ },
+ ],
+ schemas,
+);
+/**
+{
+ keys: [
+ '0xdf30dba06db6a30e65354d9a64c609861f089545ca58c6b4dbe31a5f338cb0e3',
+ '0xdf30dba06db6a30e65354d9a64c6098600000000000000000000000000000015',
+ '0xdf30dba06db6a30e65354d9a64c6098600000000000000000000000000000016',
+ ],
+ values: [
+ '0x00000000000000000000000000000017',
+ '0x983abc616f2442bab7a917e6bb8660df8b01f3bf',
+ '0x56ecbc104136d00eb37aa0dce60e075f10292d81',
+ ],
+}
+*/
+```
+
+
+
---
## encodePermissions
@@ -1611,7 +1677,7 @@ Either a string of the hexadecimal `interfaceID` as defined by [ERC165](https://
The `interfaceName` will only check for the latest version of the standard's `interfaceID`, which can be found in `src/constants/interfaces`. For LSPs, the `interfaceIDs` are taken from the latest release of the [@lukso/lsp-smart-contracts](https://github.com/lukso-network/lsp-smart-contracts) library.
-:::info
+:::
##### 2. `options` - Object (optional)
diff --git a/src/index.test.ts b/src/index.test.ts
index 561480b2..db99edaf 100644
--- a/src/index.test.ts
+++ b/src/index.test.ts
@@ -1004,6 +1004,45 @@ describe('Running @erc725/erc725.js tests...', () => {
assert.deepStrictEqual(results, intendedResult);
});
+ it('encodes subset of elements for keyType "Array" in naked class instance', () => {
+ const schemas: ERC725JSONSchema[] = [
+ {
+ name: 'AddressPermissions[]',
+ key: '0xdf30dba06db6a30e65354d9a64c609861f089545ca58c6b4dbe31a5f338cb0e3',
+ keyType: 'Array',
+ valueType: 'address',
+ valueContent: 'Address',
+ },
+ ];
+ const erc725 = new ERC725(schemas);
+ const encodedArraySection = erc725.encodeData([
+ {
+ keyName: 'AddressPermissions[]',
+ value: [
+ '0x983abc616f2442bab7a917e6bb8660df8b01f3bf',
+ '0x56ecbc104136d00eb37aa0dce60e075f10292d81',
+ ],
+ totalArrayLength: 23,
+ startingIndex: 21,
+ },
+ ]);
+
+ // Expected result with custom startingIndex and totalArrayLength
+ const expectedResult = {
+ keys: [
+ '0xdf30dba06db6a30e65354d9a64c609861f089545ca58c6b4dbe31a5f338cb0e3',
+ '0xdf30dba06db6a30e65354d9a64c6098600000000000000000000000000000015', // 21
+ '0xdf30dba06db6a30e65354d9a64c6098600000000000000000000000000000016', // 22
+ ],
+ values: [
+ '0x00000000000000000000000000000017', // 23
+ '0x983abc616f2442bab7a917e6bb8660df8b01f3bf',
+ '0x56ecbc104136d00eb37aa0dce60e075f10292d81',
+ ],
+ };
+ assert.deepStrictEqual(encodedArraySection, expectedResult);
+ });
+
it(`decode all data values for keyType "Array" in naked class instance: ${schemaElement.name}`, async () => {
const values = allGraphData.filter(
(e) => e.key.slice(0, 34) === schemaElement.key.slice(0, 34),
diff --git a/src/lib/encoder.test.ts b/src/lib/encoder.test.ts
index 07e40637..ed170fca 100644
--- a/src/lib/encoder.test.ts
+++ b/src/lib/encoder.test.ts
@@ -633,7 +633,7 @@ describe('encoder', () => {
});
});
- describe('when encoding a value that exceeds the maximal lenght of bytes than its type', () => {
+ describe('when encoding a value that exceeds the maximal length of bytes than its type', () => {
const validTestCases = [
{
valueType: 'bytes32',
@@ -796,13 +796,13 @@ describe('encoder', () => {
});
describe('when encoding uintN[CompactBytesArray]', () => {
- it('should throw if trying to encode a value that exceeds the maximal lenght of bytes for this type', async () => {
+ it('should throw if trying to encode a value that exceeds the maximal length of bytes for this type', async () => {
expect(() => {
encodeValueType('uint8[CompactBytesArray]', [15, 178, 266]);
}).to.throw('Hex uint8 value at index 2 does not fit in 1 bytes');
});
- it('should throw if trying to decode a value that exceeds the maximal lenght of bytes for this type', async () => {
+ it('should throw if trying to decode a value that exceeds the maximal length of bytes for this type', async () => {
expect(() => {
decodeValueType(
'uint8[CompactBytesArray]',
@@ -813,7 +813,7 @@ describe('encoder', () => {
});
describe('when encoding bytesN[CompactBytesArray]', () => {
- it('should throw if trying to encode a value that exceeds the maximal lenght of bytes for this type', async () => {
+ it('should throw if trying to encode a value that exceeds the maximal length of bytes for this type', async () => {
expect(() => {
encodeValueType('bytes4[CompactBytesArray]', [
'0xe6520726',
@@ -824,7 +824,7 @@ describe('encoder', () => {
}).to.throw('Hex bytes4 value at index 3 does not fit in 4 bytes');
});
- it('should throw if trying to decode a value that exceeds the maximal lenght of bytes for this type', async () => {
+ it('should throw if trying to decode a value that exceeds the maximal length of bytes for this type', async () => {
expect(() => {
decodeValueType(
'bytes4[CompactBytesArray]',
diff --git a/src/lib/utils.test.ts b/src/lib/utils.test.ts
index 8a8ee3f2..27a75b2a 100644
--- a/src/lib/utils.test.ts
+++ b/src/lib/utils.test.ts
@@ -720,6 +720,168 @@ describe('utils', () => {
});
});
+ describe('encodeData with custom array length and starting index', () => {
+ const schemas: ERC725JSONSchema[] = [
+ {
+ name: 'AddressPermissions[]',
+ key: '0xdf30dba06db6a30e65354d9a64c609861f089545ca58c6b4dbe31a5f338cb0e3',
+ keyType: 'Array',
+ valueType: 'address',
+ valueContent: 'Address',
+ },
+ ];
+
+ it('should be able to specify the array length + starting index', () => {
+ const encodedArraySection = encodeData(
+ [
+ {
+ keyName: 'AddressPermissions[]',
+ value: [
+ '0x983abc616f2442bab7a917e6bb8660df8b01f3bf',
+ '0x56ecbc104136d00eb37aa0dce60e075f10292d81',
+ ],
+ totalArrayLength: 23,
+ startingIndex: 21,
+ },
+ ],
+ schemas,
+ );
+
+ // Expected result with custom startingIndex and totalArrayLength
+ const expectedResult = {
+ keys: [
+ '0xdf30dba06db6a30e65354d9a64c609861f089545ca58c6b4dbe31a5f338cb0e3',
+ '0xdf30dba06db6a30e65354d9a64c6098600000000000000000000000000000015', // 21
+ '0xdf30dba06db6a30e65354d9a64c6098600000000000000000000000000000016', // 22
+ ],
+ values: [
+ '0x00000000000000000000000000000017', // 23
+ '0x983abc616f2442bab7a917e6bb8660df8b01f3bf',
+ '0x56ecbc104136d00eb37aa0dce60e075f10292d81',
+ ],
+ };
+
+ assert.deepStrictEqual(
+ encodedArraySection,
+ expectedResult,
+ 'Encoding with custom starting index and array length should match the expected result.',
+ );
+ });
+
+ it('should throw if startingIndex is negative', () => {
+ const encodeDataWithNegativeStartingIndex = () => {
+ encodeData(
+ [
+ {
+ keyName: 'AddressPermissions[]',
+ value: ['0x983abc616f2442bab7a917e6bb8660df8b01f3bf'],
+ totalArrayLength: 1,
+ startingIndex: -1,
+ },
+ ],
+ schemas,
+ );
+ };
+
+ assert.throws(
+ encodeDataWithNegativeStartingIndex,
+ /Invalid `startingIndex`/,
+ 'Should throw an error for negative startingIndex',
+ );
+ });
+
+ it('should throw if totalArrayLength is smaller than elements in provided value array', () => {
+ const encodeDataWithLowerTotalArrayLength = () => {
+ encodeData(
+ [
+ {
+ keyName: 'AddressPermissions[]',
+ value: [
+ '0x983abc616f2442bab7a917e6bb8660df8b01f3bf',
+ '0x56ecbc104136d00eb37aa0dce60e075f10292d81',
+ ],
+ totalArrayLength: 1, // 2 elements
+ startingIndex: 0,
+ },
+ ],
+ schemas,
+ );
+ };
+
+ assert.throws(
+ encodeDataWithLowerTotalArrayLength,
+ /Invalid `totalArrayLength`/,
+ 'Should throw an error for totalArrayLength smaller than the number of provided elements',
+ );
+ });
+
+ it('should start from 0 if startingIndex is not provided', () => {
+ const result = encodeData(
+ [
+ {
+ keyName: 'AddressPermissions[]',
+ value: ['0x983abc616f2442bab7a917e6bb8660df8b01f3bf'],
+ totalArrayLength: 1,
+ },
+ ],
+ schemas,
+ );
+
+ const expectedResult = {
+ keys: [
+ '0xdf30dba06db6a30e65354d9a64c609861f089545ca58c6b4dbe31a5f338cb0e3',
+ '0xdf30dba06db6a30e65354d9a64c6098600000000000000000000000000000000',
+ ],
+ values: [
+ '0x00000000000000000000000000000001',
+ '0x983abc616f2442bab7a917e6bb8660df8b01f3bf',
+ ],
+ };
+
+ assert.deepStrictEqual(
+ result,
+ expectedResult,
+ 'Should encode starting from index 0 if startingIndex is not provided',
+ );
+ });
+
+ it('should use the number of elements in value field if totalArrayLength is not provided', () => {
+ const result = encodeData(
+ [
+ {
+ keyName: 'AddressPermissions[]',
+ value: [
+ '0x983abc616f2442bab7a917e6bb8660df8b01f3bf',
+ '0x56ecbc104136d00eb37aa0dce60e075f10292d81',
+ ],
+ // Not specifying totalArrayLength, it should default to the number of elements in the value array
+ startingIndex: 0,
+ },
+ ],
+ schemas,
+ );
+
+ const expectedResult = {
+ keys: [
+ '0xdf30dba06db6a30e65354d9a64c609861f089545ca58c6b4dbe31a5f338cb0e3',
+ '0xdf30dba06db6a30e65354d9a64c6098600000000000000000000000000000000',
+ '0xdf30dba06db6a30e65354d9a64c6098600000000000000000000000000000001',
+ ],
+ values: [
+ '0x00000000000000000000000000000002',
+ '0x983abc616f2442bab7a917e6bb8660df8b01f3bf',
+ '0x56ecbc104136d00eb37aa0dce60e075f10292d81',
+ ],
+ };
+
+ assert.deepStrictEqual(
+ result,
+ expectedResult,
+ 'should use the number of elements in value field if totalArrayLength is not provided',
+ );
+ });
+ });
+
describe('isDataAuthentic', () => {
it('returns true if data is authentic', () => {
const data = 'h3ll0HowAreYou?';
diff --git a/src/lib/utils.ts b/src/lib/utils.ts
index 132a793f..a447f0b0 100644
--- a/src/lib/utils.ts
+++ b/src/lib/utils.ts
@@ -243,6 +243,8 @@ export function encodeKey(
| URLDataToEncode
| URLDataToEncode[]
| boolean,
+ startingIndex = 0,
+ totalArrayLength = Array.isArray(value) ? value.length : 0,
) {
// NOTE: This will not guarantee order of array as on chain. Assumes developer must set correct order
@@ -260,21 +262,45 @@ export function encodeKey(
return null;
}
+ if (
+ typeof startingIndex !== 'number' ||
+ typeof totalArrayLength !== 'number'
+ ) {
+ throw new Error(
+ 'Invalid `startingIndex` or `totalArrayLength` parameters. Values must be of type number.',
+ );
+ }
+
+ if (startingIndex < 0) {
+ throw new Error(
+ 'Invalid `startingIndex` parameter. Value cannot be negative.',
+ );
+ }
+
+ if (totalArrayLength < value.length) {
+ throw new Error(
+ 'Invalid `totalArrayLength` parameter. Array length must be at least as large as the number of elements of the value array.',
+ );
+ }
+
const results: { key: string; value: string }[] = [];
for (let index = 0; index < value.length; index++) {
- const dataElement = value[index];
if (index === 0) {
- // This is arrayLength as the first element in the raw array
+ // This is totalArrayLength as the first element in the raw array
// encoded as uint128
results.push({
key: schema.key,
- value: encodeValueType('uint128', value.length),
+ // Encode the explicitly provided or default array length
+ value: encodeValueType('uint128', totalArrayLength),
});
}
+ const elementIndex = startingIndex + index;
+ const dataElement = value[index];
+
results.push({
- key: encodeArrayKey(schema.key, index),
+ key: encodeArrayKey(schema.key, elementIndex),
value: encodeKeyValue(
schema.valueContent,
schema.valueType,
@@ -439,7 +465,10 @@ export function encodeData(
const dataAsArray = Array.isArray(data) ? data : [data];
return dataAsArray.reduce(
- (accumulator, { keyName, value, dynamicKeyParts }) => {
+ (
+ accumulator,
+ { keyName, value, dynamicKeyParts, startingIndex, totalArrayLength },
+ ) => {
let schemaElement: ERC725JSONSchema | null = null;
let encodedValue; // would be nice to type this
@@ -453,10 +482,20 @@ export function encodeData(
}
schemaElement = getSchemaElement(schema, keyName, dynamicKeyParts);
- encodedValue = encodeKey(schemaElement, value);
+ encodedValue = encodeKey(
+ schemaElement,
+ value,
+ startingIndex,
+ totalArrayLength,
+ );
} else {
schemaElement = getSchemaElement(schema, keyName);
- encodedValue = encodeKey(schemaElement, value as any);
+ encodedValue = encodeKey(
+ schemaElement,
+ value as any,
+ startingIndex,
+ totalArrayLength,
+ );
}
if (typeof encodedValue === 'string') {
diff --git a/src/types/decodeData.ts b/src/types/decodeData.ts
index b0e32342..7e3179f3 100644
--- a/src/types/decodeData.ts
+++ b/src/types/decodeData.ts
@@ -4,6 +4,8 @@ export interface DataInput {
keyName: string; // can be the name or the hex/hash
value;
dynamicKeyParts?: string | string[];
+ totalArrayLength?: number;
+ startingIndex?: number;
}
export interface EncodeDataInput extends DataInput {