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
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 encodeDataWithLowerArrayLenght = () => {
encodeData(
[
{
keyName: 'AddressPermissions[]',
value: [
'0x983abc616f2442bab7a917e6bb8660df8b01f3bf',
'0x56ecbc104136d00eb37aa0dce60e075f10292d81',
],
arrayLength: 1, // 2 elements
startingIndex: 0,
},
],
schemas,
);
};

assert.throws(
encodeDataWithLowerArrayLenght,
/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
52 changes: 46 additions & 6 deletions src/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,8 @@ export function encodeKey(
| URLDataToEncode
| URLDataToEncode[]
| boolean,
startingIndex = 0,
arrayLength = Array.isArray(value) ? value.length : 0,
) {
// NOTE: This will not guarantee order of array as on chain. Assumes developer must set correct order

Expand All @@ -260,21 +262,46 @@ export function encodeKey(
return null;
}

// Check if an starting index and array lenght are correct types
fhildeb marked this conversation as resolved.
Show resolved Hide resolved
if (
typeof startingIndex !== 'number' ||
typeof arrayLength !== 'number'
) {
throw new Error(
'Invalid startingIndex or arrayLength parameters. Values must be of type number',
fhildeb marked this conversation as resolved.
Show resolved Hide resolved
);
}

if (startingIndex < 0) {
throw new Error(
'Invalid startingIndex parameter. Value cannot be negative.',
CJ42 marked this conversation as resolved.
Show resolved Hide resolved
);
}

if (arrayLength < value.length) {
throw new Error(
'Invalid arrayLength 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
// encoded as uint128
results.push({
key: schema.key,
value: encodeValueType('uint128', value.length),
// Encode the explicitly provided or default array length
value: encodeValueType('uint128', arrayLength),
});
}

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,
Expand Down Expand Up @@ -439,7 +466,10 @@ export function encodeData(
const dataAsArray = Array.isArray(data) ? data : [data];

return dataAsArray.reduce(
(accumulator, { keyName, value, dynamicKeyParts }) => {
(
accumulator,
{ keyName, value, dynamicKeyParts, startingIndex, arrayLength },
) => {
let schemaElement: ERC725JSONSchema | null = null;
let encodedValue; // would be nice to type this

Expand All @@ -453,10 +483,20 @@ export function encodeData(
}

schemaElement = getSchemaElement(schema, keyName, dynamicKeyParts);
encodedValue = encodeKey(schemaElement, value);
encodedValue = encodeKey(
schemaElement,
value,
startingIndex,
arrayLength,
);
} else {
schemaElement = getSchemaElement(schema, keyName);
encodedValue = encodeKey(schemaElement, value as any);
encodedValue = encodeKey(
schemaElement,
value as any,
startingIndex,
arrayLength,
);
}

if (typeof encodedValue === 'string') {
Expand Down
2 changes: 2 additions & 0 deletions src/types/decodeData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ export interface DataInput {
keyName: string; // can be the name or the hex/hash
value;
dynamicKeyParts?: string | string[];
arrayLength?: number;
startingIndex?: number;
}

export interface EncodeDataInput extends DataInput {
Expand Down
Loading