Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add dynamic array encoding for encodeData #394

Merged
merged 9 commits into from
Mar 22, 2024
48 changes: 47 additions & 1 deletion docs/classes/ERC725.md
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,8 @@ An array of objects containing the following properties:
| `keyName` | string | Can be either the named key (i.e. `LSP3Profile`, `LSP12IssuedAssetsMap:<address>`) or the hashed key (with or without `0x` prefix, i.e. `0x5ef...` or `5ef...`). |
| `dynamicKeyParts` (optional) | string or <br/> string[&nbsp;] | The dynamic parts of the `keyName` that will be used for encoding the key. |
| `value` | string or <br/> string[&nbsp;] <br/> 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`. |
| `arrayLength` (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:<address>`. In that case, the value should also set the `dynamicKeyParts` property:

Expand Down Expand Up @@ -700,6 +702,50 @@ myErc725.encodeData([

</details>

<details>
<summary>Encode a subset of array elements</summary>

```javascript title="Encode a subset of array elements"
const schemas = [
{
name: 'AddressPermissions[]',
key: '0xdf30dba06db6a30e65354d9a64c609861f089545ca58c6b4dbe31a5f338cb0e3',
keyType: 'Array',
valueType: 'address',
valueContent: 'Address',
},
];

const myErc725 = new ERC725(schemas);
fhildeb marked this conversation as resolved.
Show resolved Hide resolved
myErc725.encodeData([
{
keyName: 'AddressPermissions[]',
value: [
'0x983abc616f2442bab7a917e6bb8660df8b01f3bf',
'0x56ecbc104136d00eb37aa0dce60e075f10292d81',
],
arrayLength: 23,
startingIndex: 21,
},
]);
/**
{
keys: [
'0xdf30dba06db6a30e65354d9a64c609861f089545ca58c6b4dbe31a5f338cb0e3',
'0xdf30dba06db6a30e65354d9a64c6098600000000000000000000000000000015',
'0xdf30dba06db6a30e65354d9a64c6098600000000000000000000000000000016',
],
values: [
'0x00000000000000000000000000000017',
'0x983abc616f2442bab7a917e6bb8660df8b01f3bf',
'0x56ecbc104136d00eb37aa0dce60e075f10292d81',
],
}
*/
```

</details>

---

## encodePermissions
Expand Down Expand Up @@ -1611,7 +1657,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)

Expand Down
39 changes: 39 additions & 0 deletions src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1004,6 +1004,45 @@ describe('Running @erc725/erc725.js tests...', () => {
assert.deepStrictEqual(results, intendedResult);
});

it('encodes dynamic data values for keyType "Array" in naked class instance', () => {
fhildeb marked this conversation as resolved.
Show resolved Hide resolved
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',
],
arrayLength: 23,
startingIndex: 21,
},
]);

// Expected result with custom startingIndex and arrayLength
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),
Expand Down
10 changes: 5 additions & 5 deletions src/lib/encoder.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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]',
Expand All @@ -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',
Expand All @@ -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]',
Expand Down
162 changes: 162 additions & 0 deletions src/lib/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
],
arrayLength: 23,
fhildeb marked this conversation as resolved.
Show resolved Hide resolved
startingIndex: 21,
},
],
schemas,
);

// Expected result with custom startingIndex and arrayLength
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'],
arrayLength: 1,
startingIndex: -1,
},
],
schemas,
);
};

assert.throws(
encodeDataWithNegativeStartingIndex,
/Invalid startingIndex/,
'Should throw an error for negative startingIndex',
);
});

it('should throw if arrayLength is smaller than elements in provided value array', () => {
const encodeDataWithLowerArrayLength = () => {
encodeData(
[
{
keyName: 'AddressPermissions[]',
value: [
'0x983abc616f2442bab7a917e6bb8660df8b01f3bf',
'0x56ecbc104136d00eb37aa0dce60e075f10292d81',
],
arrayLength: 1, // 2 elements
startingIndex: 0,
},
],
schemas,
);
};

assert.throws(
encodeDataWithLowerArrayLength,
/Invalid arrayLength/,
'Should throw an error for arrayLength smaller than the number of provided elements',
);
});

it('should start from 0 if startingIndex is not provided', () => {
const result = encodeData(
[
{
keyName: 'AddressPermissions[]',
value: ['0x983abc616f2442bab7a917e6bb8660df8b01f3bf'],
arrayLength: 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 arrayLength is not provided', () => {
const result = encodeData(
[
{
keyName: 'AddressPermissions[]',
value: [
'0x983abc616f2442bab7a917e6bb8660df8b01f3bf',
'0x56ecbc104136d00eb37aa0dce60e075f10292d81',
],
// Not specifying arrayLength, 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 arrayLength is not provided',
);
});
});

describe('isDataAuthentic', () => {
it('returns true if data is authentic', () => {
const data = 'h3ll0HowAreYou?';
Expand Down
Loading
Loading