From 3ac4a3b5be5bf8090375239431e44736ff175db8 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Mon, 22 Oct 2018 19:05:05 +0200 Subject: [PATCH] Address type cleanups (#337) * AccountIndex allows only <= 0xef for 1 byte * Max 1-byte constructed from prefix * Expand Address tests, add ts checks --- packages/types/src/AccountId.spec.js | 55 ---------- packages/types/src/AccountId.spec.ts | 57 ++++++++++ ...ountIndex.spec.js => AccountIndex.spec.ts} | 29 ++++- packages/types/src/AccountIndex.ts | 51 +++++---- packages/types/src/Address.spec.js | 99 ----------------- packages/types/src/Address.spec.ts | 102 ++++++++++++++++++ packages/types/src/Address.ts | 4 +- 7 files changed, 220 insertions(+), 177 deletions(-) delete mode 100644 packages/types/src/AccountId.spec.js create mode 100644 packages/types/src/AccountId.spec.ts rename packages/types/src/{AccountIndex.spec.js => AccountIndex.spec.ts} (50%) delete mode 100644 packages/types/src/Address.spec.js create mode 100644 packages/types/src/Address.spec.ts diff --git a/packages/types/src/AccountId.spec.js b/packages/types/src/AccountId.spec.js deleted file mode 100644 index 836c6a9d8a..0000000000 --- a/packages/types/src/AccountId.spec.js +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2017-2018 @polkadot/types authors & contributors -// This software may be modified and distributed under the terms -// of the ISC license. See the LICENSE file for details. - -import AccountId from './AccountId'; -import U8a from './codec/U8a'; - - -const testDecode = (type, input, expected) => - it(`can decode from ${type}`, () => { - const a = new AccountId(input); - expect(a.toString()).toBe(expected); - }); - -const testEncode = (to, expected) => - it(`can encode ${to}`, () => { - const a = new AccountId('5C62W7ELLAAfix9LYrcx5smtcffbhvThkM5x7xfMeYXCt72s'); - expect(a[to]()).toEqual(expected); - }); - - -describe('AccountId', () => { - - testDecode( - 'AccountId', - new AccountId('0x0102030405060708010203040506070801020304050607080102030405060708'), - '5C62W7ELLAAfix9LYrcx5smtcffbhvThkM5x7xfMeYXCt72s' - ); - testDecode('hex', '0x0102030405060708010203040506070801020304050607080102030405060708', '5C62W7ELLAAfix9LYrcx5smtcffbhvThkM5x7xfMeYXCt72s'); - testDecode('string', '5C62W7ELLAAfix9LYrcx5smtcffbhvThkM5x7xfMeYXCt72s', '5C62W7ELLAAfix9LYrcx5smtcffbhvThkM5x7xfMeYXCt72s'); - testDecode( - 'U8a', - new U8a([ - 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, - 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8 - ]), - '5C62W7ELLAAfix9LYrcx5smtcffbhvThkM5x7xfMeYXCt72s' - ); - testDecode( - 'Uint8Array', - Uint8Array.from([ - 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, - 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8 - ]), - '5C62W7ELLAAfix9LYrcx5smtcffbhvThkM5x7xfMeYXCt72s' - ); - - testEncode('toHex', '0x0102030405060708010203040506070801020304050607080102030405060708'); - testEncode('toJSON', '5C62W7ELLAAfix9LYrcx5smtcffbhvThkM5x7xfMeYXCt72s'); - testEncode('toString', '5C62W7ELLAAfix9LYrcx5smtcffbhvThkM5x7xfMeYXCt72s'); - testEncode('toU8a', Uint8Array.from([ - 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, - 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8 - ])); -}); diff --git a/packages/types/src/AccountId.spec.ts b/packages/types/src/AccountId.spec.ts new file mode 100644 index 0000000000..5b4f0872ee --- /dev/null +++ b/packages/types/src/AccountId.spec.ts @@ -0,0 +1,57 @@ +// Copyright 2017-2018 @polkadot/types authors & contributors +// This software may be modified and distributed under the terms +// of the ISC license. See the LICENSE file for details. + +import AccountId from './AccountId'; +import U8a from './codec/U8a'; + +describe('AccountId', () => { + describe('decoding', () => { + const testDecode = (type: string, input: Uint8Array | string | AccountId | U8a, expected: string) => + it(`can decode from ${type}`, () => { + const a = new AccountId(input); + expect(a.toString()).toBe(expected); + }); + + testDecode( + 'AccountId', + new AccountId('0x0102030405060708010203040506070801020304050607080102030405060708'), + '5C62W7ELLAAfix9LYrcx5smtcffbhvThkM5x7xfMeYXCt72s' + ); + testDecode('hex', '0x0102030405060708010203040506070801020304050607080102030405060708', '5C62W7ELLAAfix9LYrcx5smtcffbhvThkM5x7xfMeYXCt72s'); + testDecode('string', '5C62W7ELLAAfix9LYrcx5smtcffbhvThkM5x7xfMeYXCt72s', '5C62W7ELLAAfix9LYrcx5smtcffbhvThkM5x7xfMeYXCt72s'); + testDecode( + 'U8a', + new U8a([ + 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, + 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8 + ]), + '5C62W7ELLAAfix9LYrcx5smtcffbhvThkM5x7xfMeYXCt72s' + ); + testDecode( + 'Uint8Array', + Uint8Array.from([ + 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, + 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8 + ]), + '5C62W7ELLAAfix9LYrcx5smtcffbhvThkM5x7xfMeYXCt72s' + ); + }); + + describe('encoding', () => { + const testEncode = (to: 'toHex' | 'toJSON' | 'toString' | 'toU8a', expected: Uint8Array | string) => + it(`can encode ${to}`, () => { + const a = new AccountId('5C62W7ELLAAfix9LYrcx5smtcffbhvThkM5x7xfMeYXCt72s'); + + expect(a[to]()).toEqual(expected); + }); + + testEncode('toHex', '0x0102030405060708010203040506070801020304050607080102030405060708'); + testEncode('toJSON', '5C62W7ELLAAfix9LYrcx5smtcffbhvThkM5x7xfMeYXCt72s'); + testEncode('toString', '5C62W7ELLAAfix9LYrcx5smtcffbhvThkM5x7xfMeYXCt72s'); + testEncode('toU8a', Uint8Array.from([ + 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, + 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8 + ])); + }); +}); diff --git a/packages/types/src/AccountIndex.spec.js b/packages/types/src/AccountIndex.spec.ts similarity index 50% rename from packages/types/src/AccountIndex.spec.js rename to packages/types/src/AccountIndex.spec.ts index c14675d1e5..6712e2fe5b 100644 --- a/packages/types/src/AccountIndex.spec.js +++ b/packages/types/src/AccountIndex.spec.ts @@ -12,6 +12,12 @@ describe('AccountIndex', () => { ).toEqual('336794129'); }); + it('creates a BN representation (from ss-58)', () => { + expect( + new AccountIndex('Mwz15xN8').toBn().toString() + ).toEqual('336794129'); + }); + it('constructs 2-byte from number', () => { expect( new AccountIndex(256 * 1).toString() @@ -21,6 +27,27 @@ describe('AccountIndex', () => { it('constructs from number', () => { expect( new AccountIndex(new BN(336794129)).toString() - ).toEqual('3N5RJXxJhgRhxu'); + ).toEqual('Mwz15xN8'); + }); + + describe('calcLength', () => { + const testLength = (value: number, length: number) => + expect(AccountIndex.calcLength(value)).toEqual(length); + + it('returns 1 for <= 0xef', () => { + testLength(0xef, 1); + }); + + it('returns 2 for > 0xef', () => { + testLength(0xf0, 2); + }); + + it('returns 4 bytes for 32-bit inputs', () => { + testLength(0xffeeddcc, 4); + }); + + it('returns 8 bytes for larger inputs', () => { + testLength(0x122334455, 8); + }); }); }); diff --git a/packages/types/src/AccountIndex.ts b/packages/types/src/AccountIndex.ts index 6a25ed96ef..07c1e0c297 100644 --- a/packages/types/src/AccountIndex.ts +++ b/packages/types/src/AccountIndex.ts @@ -11,6 +11,14 @@ import U8a from './codec/U8a'; export const ENUMSET_SIZE = new BN(64); +const PREFIX_1BYTE = 0xef; +const PREFIX_2BYTE = 0xfc; +const PREFIX_4BYTE = 0xfd; +const PREFIX_8BYTE = 0xfe; +const MAX_1BYTE = new BN(PREFIX_1BYTE); +const MAX_2BYTE = new BN(1).shln(16); +const MAX_4BYTE = new BN(1).shln(32); + // A wrapper around an AccountIndex, which is a shortened, variable-length encoding // for an Account. We extends from U8a which is basically // just a Uint8Array wrapper. @@ -35,20 +43,9 @@ export default class AccountIndex extends U8a { return value.subarray(offset, offset + length); } else if (isNumber(value) || isBn(value)) { - const index = bnToBn(value); - const bitLength = index.ltn(1 << 8) - ? 8 - : ( - index.ltn(1 << 16) - ? 16 - : ( - index.ltn(1 << 32) - ? 32 - : 64 - ) - ); + const bitLength = 8 * AccountIndex.calcLength(value); - return bnToU8a(index, bitLength, true); + return bnToU8a(value, bitLength, true); } else if (isHex(value)) { return hexToU8a(value); } @@ -56,16 +53,30 @@ export default class AccountIndex extends U8a { return decodeAddress(value); } + static calcLength (_value: BN | number): number { + const value = bnToBn(_value); + + if (value.lte(MAX_1BYTE)) { + return 1; + } else if (value.lt(MAX_2BYTE)) { + return 2; + } else if (value.lt(MAX_4BYTE)) { + return 4; + } + + return 8; + } + static readLength (input: Uint8Array): [number, number] { const first = input[0]; - if (first <= 0xef) { + if (first <= PREFIX_1BYTE) { return [0, 1]; - } else if (first === 0xfc) { + } else if (first === PREFIX_2BYTE) { return [1, 2]; - } else if (first === 0xfd) { + } else if (first === PREFIX_4BYTE) { return [1, 4]; - } else if (first === 0xfe) { + } else if (first === PREFIX_8BYTE) { return [1, 8]; } @@ -74,9 +85,9 @@ export default class AccountIndex extends U8a { static writeLength (input: Uint8Array): Uint8Array { switch (input.length) { - case 2: return new Uint8Array([0xfc]); - case 4: return new Uint8Array([0xfd]); - case 8: return new Uint8Array([0xfe]); + case 2: return new Uint8Array([PREFIX_2BYTE]); + case 4: return new Uint8Array([PREFIX_4BYTE]); + case 8: return new Uint8Array([PREFIX_8BYTE]); default: return new Uint8Array([]); } } diff --git a/packages/types/src/Address.spec.js b/packages/types/src/Address.spec.js deleted file mode 100644 index ed518d8b1f..0000000000 --- a/packages/types/src/Address.spec.js +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright 2017-2018 @polkadot/types authors & contributors -// This software may be modified and distributed under the terms -// of the ISC license. See the LICENSE file for details. - -import AccountId from './AccountId'; -import AccountIndex from './AccountIndex'; -import Address from './Address'; - -const testDecode = (type, input, expected) => - it(`can decode from ${type}`, () => { - const a = new Address(input); - expect(a.toString()).toBe(expected); - }); - -const testEncode = (to, expected) => - it(`can encode ${to}`, () => { - const a = new Address('5C62W7ELLAAfix9LYrcx5smtcffbhvThkM5x7xfMeYXCt72s'); - expect(a[to]()).toEqual(expected); - }); - -describe('Address', () => { - testDecode( - 'Address', - new Address('5C62W7ELLAAfix9LYrcx5smtcffbhvThkM5x7xfMeYXCt72s'), - '5C62W7ELLAAfix9LYrcx5smtcffbhvThkM5x7xfMeYXCt72s' - ); - testDecode( - 'AccountId', - new AccountId('5C62W7ELLAAfix9LYrcx5smtcffbhvThkM5x7xfMeYXCt72s'), - '5C62W7ELLAAfix9LYrcx5smtcffbhvThkM5x7xfMeYXCt72s' - ); - testDecode( - 'AccountIndex', - new Address('PrF3'), - // NOTE Expected adress here is encoded with prefix 42, input above with 68 - 'F7ih' - ); - testDecode( - 'AccountIndex', - new AccountIndex('0x0001'), - '25GUyk' - ); - testDecode( - 'Array', - [ - 255, - 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, - 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8 - ], - '5C62W7ELLAAfix9LYrcx5smtcffbhvThkM5x7xfMeYXCt72s' - ); - testDecode( - 'Uint8Array (with prefix 255)', - Uint8Array.from([ - 255, - 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, - 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8 - ]), - '5C62W7ELLAAfix9LYrcx5smtcffbhvThkM5x7xfMeYXCt72s' - ); - testDecode( - 'Uint8Array (with prefix 1 byte)', - Uint8Array.from([1]), - 'F7L6' - ); - testDecode( - 'Uint8Array (with prefix 2 bytes)', - Uint8Array.from([0xfc, 0, 1]), - '25GUyk' - ); - testDecode( - 'Uint8Array (with prefix 4 bytes)', - Uint8Array.from([0xfd, 17, 18, 19, 20]), - 'Mwz15xN8' - ); - testDecode( - 'Uint8Array (with prefix 8 bytes)', - Uint8Array.from([0xfe, 17, 18, 19, 20, 21, 22, 23, 24]), - '3N5RJXxM5fLd4h' - ); - - // FIXME Add this - // testEncode( - // 'toHex', - // '0x0102030405060708010203040506070801020304050607080102030405060708' - // ); - testEncode( - 'toString', - '5C62W7ELLAAfix9LYrcx5smtcffbhvThkM5x7xfMeYXCt72s' - ); - testEncode( - 'toU8a', - Uint8Array.from([ - 255, - 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, - 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8 - ]) - ); -}); diff --git a/packages/types/src/Address.spec.ts b/packages/types/src/Address.spec.ts new file mode 100644 index 0000000000..a17d77df88 --- /dev/null +++ b/packages/types/src/Address.spec.ts @@ -0,0 +1,102 @@ +// Copyright 2017-2018 @polkadot/types authors & contributors +// This software may be modified and distributed under the terms +// of the ISC license. See the LICENSE file for details. + +import AccountId from './AccountId'; +import AccountIndex from './AccountIndex'; +import Address from './Address'; + +describe('Address', () => { + const testDecode = (type: string, input: Address | AccountId | AccountIndex | Array | Uint8Array, expected: string) => + it(`can decode from ${type}`, () => { + const a = new Address(input); + expect(a.toString()).toBe(expected); + }); + + describe('decoding', () => { + testDecode( + 'Address', + new Address('5C62W7ELLAAfix9LYrcx5smtcffbhvThkM5x7xfMeYXCt72s'), + '5C62W7ELLAAfix9LYrcx5smtcffbhvThkM5x7xfMeYXCt72s' + ); + testDecode( + 'AccountId', + new AccountId('5C62W7ELLAAfix9LYrcx5smtcffbhvThkM5x7xfMeYXCt72s'), + '5C62W7ELLAAfix9LYrcx5smtcffbhvThkM5x7xfMeYXCt72s' + ); + testDecode( + 'AccountIndex', + new Address('PrF3'), + // NOTE Expected adress here is encoded with prefix 42, input above with 68 + 'F7ih' + ); + testDecode( + 'AccountIndex', + new AccountIndex('0x0001'), + '25GUyk' + ); + testDecode( + 'Array', + [ + 255, + 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, + 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8 + ], + '5C62W7ELLAAfix9LYrcx5smtcffbhvThkM5x7xfMeYXCt72s' + ); + testDecode( + 'Uint8Array (with prefix 255)', + Uint8Array.from([ + 255, + 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, + 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8 + ]), + '5C62W7ELLAAfix9LYrcx5smtcffbhvThkM5x7xfMeYXCt72s' + ); + testDecode( + 'Uint8Array (with prefix 1 byte)', + Uint8Array.from([1]), + 'F7L6' + ); + testDecode( + 'Uint8Array (with prefix 2 bytes)', + Uint8Array.from([0xfc, 0, 1]), + '25GUyk' + ); + testDecode( + 'Uint8Array (with prefix 4 bytes)', + Uint8Array.from([0xfd, 17, 18, 19, 20]), + 'Mwz15xN8' + ); + testDecode( + 'Uint8Array (with prefix 8 bytes)', + Uint8Array.from([0xfe, 17, 18, 19, 20, 21, 22, 23, 24]), + '3N5RJXxM5fLd4h' + ); + }); + + describe('encoding', () => { + const testEncode = (to: 'toHex' | 'toString' | 'toU8a', expected: string | Uint8Array) => + it(`can encode ${to}`, () => { + const a = new Address('5C62W7ELLAAfix9LYrcx5smtcffbhvThkM5x7xfMeYXCt72s'); + expect(a[to]()).toEqual(expected); + }); + + testEncode( + 'toHex', + '0xff0102030405060708010203040506070801020304050607080102030405060708' + ); + testEncode( + 'toString', + '5C62W7ELLAAfix9LYrcx5smtcffbhvThkM5x7xfMeYXCt72s' + ); + testEncode( + 'toU8a', + Uint8Array.from([ + 255, + 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, + 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8 + ]) + ); + }); +}); diff --git a/packages/types/src/Address.ts b/packages/types/src/Address.ts index 92a808d967..3f6cdb7576 100644 --- a/packages/types/src/Address.ts +++ b/packages/types/src/Address.ts @@ -17,13 +17,13 @@ const ACCOUNT_ID_PREFIX = new Uint8Array([0xff]); // is encoded as // [ , ...publicKey/...bytes ] export default class Address extends Base { - constructor (value: Address | AccountId | AccountIndex | Uint8Array | string = new Uint8Array()) { + constructor (value: Address | AccountId | AccountIndex | Array | Uint8Array | string = new Uint8Array()) { super( Address.decodeAddress(value) ); } - static decodeAddress (value: Address | AccountId | AccountIndex | Uint8Array | string): AccountId | AccountIndex { + static decodeAddress (value: Address | AccountId | AccountIndex | Array | Uint8Array | string): AccountId | AccountIndex { if (value instanceof Address) { return value.raw; } else if (value instanceof AccountId || value instanceof AccountIndex) {