Skip to content

Commit

Permalink
feat(tx-builder): validate address and enum fields in runtime
Browse files Browse the repository at this point in the history
  • Loading branch information
davidyuk committed Dec 26, 2022
1 parent 31a13c6 commit 5898b3a
Show file tree
Hide file tree
Showing 6 changed files with 116 additions and 95 deletions.
32 changes: 20 additions & 12 deletions src/tx/builder/field-types/address.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { PrefixNotFoundError, TagNotFoundError } from '../../../utils/errors';
import { ArgumentError, PrefixNotFoundError, TagNotFoundError } from '../../../utils/errors';
import { toBytes } from '../../../utils/bytes';
import { decode, encode, Encoding } from '../../../utils/encoder';
import { isItemOfArray } from '../../../utils/other';
Expand All @@ -9,7 +9,7 @@ import { isItemOfArray } from '../../../utils/other';
* @see {@link https://github.com/aeternity/aeserialization/blob/eb68fe331bd476910394966b7f5ede7a74d37e35/src/aeser_id.erl#L97-L102}
* @see {@link https://github.com/aeternity/aeserialization/blob/eb68fe331bd476910394966b7f5ede7a74d37e35/src/aeser_api_encoder.erl#L163-L168}
*/
const idTagToEncoding = [
export const idTagToEncoding = [
Encoding.AccountAddress,
Encoding.Name,
Encoding.Commitment,
Expand All @@ -20,20 +20,25 @@ const idTagToEncoding = [

export type AddressEncodings = typeof idTagToEncoding[number];

export default function genAddressField<Encoding extends AddressEncodings>(): {
serialize: (value: `${Encoding}_${string}`) => Buffer;
deserialize: (value: Buffer) => `${Encoding}_${string}`;
} {
export default function genAddressField<Encoding extends AddressEncodings>(
...encodings: Encoding[]
): {
serialize: (value: `${Encoding}_${string}`) => Buffer;
deserialize: (value: Buffer) => `${Encoding}_${string}`;
} {
return {
/**
* Utility function to create and _id type
* @param hashId - Encoded hash
* @returns Buffer Buffer with ID tag and decoded HASh
*/
serialize(hashId) {
const encoding = hashId.slice(0, 2);
if (!isItemOfArray(encoding, idTagToEncoding)) throw new TagNotFoundError(encoding);
const idTag = idTagToEncoding.indexOf(encoding) + 1;
const enc = hashId.slice(0, 2);
if (!isItemOfArray(enc, idTagToEncoding)) throw new TagNotFoundError(enc);
if (!isItemOfArray(enc, encodings)) {
throw new ArgumentError('Address encoding', encodings.join(', '), enc);
}
const idTag = idTagToEncoding.indexOf(enc) + 1;
return Buffer.from([...toBytes(idTag), ...decode(hashId)]);
},

Expand All @@ -44,9 +49,12 @@ export default function genAddressField<Encoding extends AddressEncodings>(): {
*/
deserialize(buf) {
const idTag = Buffer.from(buf).readUIntBE(0, 1);
const encoding = idTagToEncoding[idTag - 1];
if (encoding == null) throw new PrefixNotFoundError(idTag);
return encode(buf.subarray(1), encoding) as `${Encoding}_${string}`;
const enc = idTagToEncoding[idTag - 1];
if (enc == null) throw new PrefixNotFoundError(idTag);
if (!isItemOfArray(enc, encodings)) {
throw new ArgumentError('Address encoding', encodings.join(', '), enc);
}
return encode(buf.subarray(1), enc) as `${Encoding}_${string}`;
},
};
}
12 changes: 7 additions & 5 deletions src/tx/builder/field-types/addresses.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import genAddressField, { AddressEncodings } from './address';

export default function genAddressesField<Encoding extends AddressEncodings>(): {
serialize: (value: Array<`${Encoding}_${string}`>) => Buffer[];
deserialize: (value: Buffer[]) => Array<`${Encoding}_${string}`>;
} {
const address = genAddressField<Encoding>();
export default function genAddressesField<Encoding extends AddressEncodings>(
...encodings: Encoding[]
): {
serialize: (value: Array<`${Encoding}_${string}`>) => Buffer[];
deserialize: (value: Buffer[]) => Array<`${Encoding}_${string}`>;
} {
const address = genAddressField(...encodings);

return {
serialize(addresses) {
Expand Down
19 changes: 15 additions & 4 deletions src/tx/builder/field-types/enumeration.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,32 @@
import { ArgumentError } from '../../../utils/errors';
import { isItemOfArray } from '../../../utils/other';

export default function genEnumerationField<Enum extends number>(): {
serialize: (value: Enum) => Buffer;
deserialize: (value: Buffer) => Enum;
export default function genEnumerationField<
Enum extends { [key: string]: number | string },
>(enm: Enum): {
serialize: (value: Enum[keyof Enum]) => Buffer;
deserialize: (value: Buffer) => Enum[keyof Enum];
} {
const values = Object.values(enm).filter((v) => typeof v === 'number');
return {
serialize(value) {
if (typeof value !== 'number') throw new ArgumentError('value', 'to be a number', value);
if (value > 0xff) throw new ArgumentError('value', 'to be less than 256', value);
if (!isItemOfArray(value, values)) {
throw new ArgumentError('value', 'to be a value of Enum', value);
}
return Buffer.from([value]);
},

deserialize(buffer) {
if (buffer.length !== 1) {
throw new ArgumentError('buffer', 'to have single element', buffer.length);
}
return buffer[0] as Enum;
const value = buffer[0];
if (!isItemOfArray(value, values)) {
throw new ArgumentError('value', 'to be a value of Enum', value);
}
return value as Enum[keyof Enum];
},
};
}
2 changes: 1 addition & 1 deletion src/tx/builder/field-types/name-id.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { produceNameId, isNameValid } from '../helpers';
import address from './address';
import { Encoded, Encoding } from '../../../utils/encoder';

const addressName = address<Encoding.Name>();
const addressName = address(Encoding.Name);

export default {
...addressName,
Expand Down
4 changes: 2 additions & 2 deletions src/tx/builder/field-types/pointers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import { NamePointer as NamePointerString } from '../../../apis/node';
import { toBytes } from '../../../utils/bytes';
import { Encoded } from '../../../utils/encoder';
import { IllegalArgumentError } from '../../../utils/errors';
import address, { AddressEncodings } from './address';
import address, { AddressEncodings, idTagToEncoding } from './address';

const addressAny = address<AddressEncodings>();
const addressAny = address(...idTagToEncoding);

// TODO: remove after fixing node types
type NamePointer = NamePointerString & {
Expand Down

0 comments on commit 5898b3a

Please sign in to comment.