diff --git a/packages/types/src/Bytes.ts b/packages/types/src/Bytes.ts index f493a4a40a68..f4998da1d556 100644 --- a/packages/types/src/Bytes.ts +++ b/packages/types/src/Bytes.ts @@ -16,11 +16,11 @@ export default class Bytes extends U8a { } byteLength (): number { - return this.length + Compact.encode(this.length).length; + return this.length + Compact.encodeU8a(this.length).length; } fromU8a (input: Uint8Array): Bytes { - const [offset, length] = Compact.decode(input); + const [offset, length] = Compact.decodeU8a(input); super.fromU8a(input.subarray(offset, offset + length.toNumber())); @@ -31,7 +31,7 @@ export default class Bytes extends U8a { return isBare ? this.raw : u8aConcat( - Compact.encode(this.length), + Compact.encodeU8a(this.length), this.raw ); } diff --git a/packages/types/src/Compact128.ts b/packages/types/src/Compact128.ts new file mode 100644 index 000000000000..42e1e5ef97eb --- /dev/null +++ b/packages/types/src/Compact128.ts @@ -0,0 +1,13 @@ +// 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 { AnyNumber } from './types'; + +import Compact from './codec/Compact'; + +export default class Compact128 extends Compact { + constructor (value?: AnyNumber) { + super(value, 128); + } +} diff --git a/packages/types/src/Compact16.ts b/packages/types/src/Compact16.ts new file mode 100644 index 000000000000..613a9ed39eb2 --- /dev/null +++ b/packages/types/src/Compact16.ts @@ -0,0 +1,13 @@ +// 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 { AnyNumber } from './types'; + +import Compact from './codec/Compact'; + +export default class Compact16 extends Compact { + constructor (value?: AnyNumber) { + super(value, 16, false); + } +} diff --git a/packages/types/src/Compact256.ts b/packages/types/src/Compact256.ts new file mode 100644 index 000000000000..def04a2597f8 --- /dev/null +++ b/packages/types/src/Compact256.ts @@ -0,0 +1,13 @@ +// 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 { AnyNumber } from './types'; + +import Compact from './codec/Compact'; + +export default class Compact256 extends Compact { + constructor (value?: AnyNumber) { + super(value, 256); + } +} diff --git a/packages/types/src/Compact32.ts b/packages/types/src/Compact32.ts new file mode 100644 index 000000000000..4e0513a09dca --- /dev/null +++ b/packages/types/src/Compact32.ts @@ -0,0 +1,13 @@ +// 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 { AnyNumber } from './types'; + +import Compact from './codec/Compact'; + +export default class Compact32 extends Compact { + constructor (value?: AnyNumber) { + super(value, 32, false); + } +} diff --git a/packages/types/src/Compact64.ts b/packages/types/src/Compact64.ts new file mode 100644 index 000000000000..74c36c8d96ae --- /dev/null +++ b/packages/types/src/Compact64.ts @@ -0,0 +1,13 @@ +// 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 { AnyNumber } from './types'; + +import Compact from './codec/Compact'; + +export default class Compact64 extends Compact { + constructor (value?: AnyNumber) { + super(value, 64, false); + } +} diff --git a/packages/types/src/Extrinsic.ts b/packages/types/src/Extrinsic.ts index 5705af00e904..34a3ced5d355 100644 --- a/packages/types/src/Extrinsic.ts +++ b/packages/types/src/Extrinsic.ts @@ -82,7 +82,7 @@ export default class Extrinsic extends Struct { byteLength (): number { const length = this.length; - return length + Compact.encode(length).length; + return length + Compact.encodeU8a(length).length; } fromJSON (input: any): Extrinsic { @@ -92,7 +92,7 @@ export default class Extrinsic extends Struct { } fromU8a (input: Uint8Array): Extrinsic { - const [offset, length] = Compact.decode(input); + const [offset, length] = Compact.decodeU8a(input); super.fromU8a(input.subarray(offset, offset + length.toNumber())); @@ -111,7 +111,7 @@ export default class Extrinsic extends Struct { return isBare ? encoded : u8aConcat( - Compact.encode(encoded.length), + Compact.encodeU8a(encoded.length), encoded ); } diff --git a/packages/types/src/Text.ts b/packages/types/src/Text.ts index e1b150f9918b..3b5bc80c9e83 100644 --- a/packages/types/src/Text.ts +++ b/packages/types/src/Text.ts @@ -31,11 +31,11 @@ export default class Text extends Base { } byteLength (): number { - return this.length + Compact.encode(this.length).length; + return this.length + Compact.encodeU8a(this.length).length; } fromU8a (input: Uint8Array): Text { - const [offset, length] = Compact.decode(input); + const [offset, length] = Compact.decodeU8a(input); this.raw = u8aToUtf8(input.subarray(offset, offset + length.toNumber())); @@ -62,7 +62,7 @@ export default class Text extends Base { return isBare ? encoded : u8aConcat( - Compact.encode(this.length), + Compact.encodeU8a(this.length), encoded ); } diff --git a/packages/types/src/codec/Compact.spec.js b/packages/types/src/codec/Compact.spec.js index 5d10e6c718fc..0bf6eec239c2 100644 --- a/packages/types/src/codec/Compact.spec.js +++ b/packages/types/src/codec/Compact.spec.js @@ -8,10 +8,10 @@ import Compact from './Compact'; import UInt from './UInt'; describe('Compact', () => { - describe('encode', () => { + describe('encodeU8a', () => { it('encodes short u8', () => { expect( - Compact.encode(18) + Compact.encodeU8a(18) ).toEqual( new Uint8Array([18 << 2]) ); @@ -19,7 +19,7 @@ describe('Compact', () => { it('encodes max u8 values', () => { expect( - Compact.encode(new UInt(63)) + Compact.encodeU8a(new UInt(63)) ).toEqual( new Uint8Array([0b11111100]) ); @@ -27,7 +27,7 @@ describe('Compact', () => { it('encodes basic u16 value', () => { expect( - Compact.encode(511) + Compact.encodeU8a(511) ).toEqual( new Uint8Array([0b11111101, 0b00000111]) ); @@ -35,7 +35,7 @@ describe('Compact', () => { it('encodes basic ua6 (not at edge)', () => { expect( - Compact.encode(111) + Compact.encodeU8a(111) ).toEqual( new Uint8Array([0xbd, 0x01]) ); @@ -43,7 +43,7 @@ describe('Compact', () => { it('encodes basic u32 values (short)', () => { expect( - Compact.encode(0xffff) + Compact.encodeU8a(0xffff) ).toEqual( new Uint8Array([254, 255, 3, 0]) ); @@ -51,36 +51,60 @@ describe('Compact', () => { it('encodes basic u32 values (full)', () => { expect( - Compact.encode(0xfffffff9) + Compact.encodeU8a(0xfffffff9, 32) ).toEqual( new Uint8Array([3, 249, 255, 255, 255]) ); }); }); - describe('decode', () => { + describe('decodeU8a', () => { it('decoded u8 value', () => { expect( - Compact.decode(new Uint8Array([0b11111100])) + Compact.decodeU8a(new Uint8Array([0b11111100])) ).toEqual([1, new BN(63)]); }); it('decodes from same u16 encoded value', () => { expect( - Compact.decode(new Uint8Array([0b11111101, 0b00000111])) + Compact.decodeU8a(new Uint8Array([0b11111101, 0b00000111])) ).toEqual([2, new BN(511)]); }); it('decodes from same u32 encoded value (short)', () => { expect( - Compact.decode(new Uint8Array([254, 255, 3, 0])) + Compact.decodeU8a(new Uint8Array([254, 255, 3, 0])) ).toEqual([4, new BN(0xffff)]); }); it('decodes from same u32 encoded value (full)', () => { expect( - Compact.decode(new Uint8Array([3, 249, 255, 255, 255])) + Compact.decodeU8a(new Uint8Array([3, 249, 255, 255, 255]), 32) ).toEqual([5, new BN(0xfffffff9)]); }); + + it('decodes from same u32 as u64 encoded value (full, default)', () => { + expect( + Compact.decodeU8a(new Uint8Array([3, 249, 255, 255, 255])) + ).toEqual([9, new BN(0xfffffff9)]); + }); + }); + + it('has the correct byteLength for constructor values (default)', () => { + expect( + new Compact(0xfffffff9).byteLength() + ).toEqual(9); + }); + + it('has the correct byteLength for constructor values (u32)', () => { + expect( + new Compact(0xfffffff9, 32).byteLength() + ).toEqual(5); + }); + + it('constructs properly via fromU8a', () => { + expect( + new Compact().fromU8a(new Uint8Array([254, 255, 3, 0])).raw + ).toEqual(new BN(0xffff)); }); }); diff --git a/packages/types/src/codec/Compact.ts b/packages/types/src/codec/Compact.ts index dab43177af26..47ca9199f87e 100644 --- a/packages/types/src/codec/Compact.ts +++ b/packages/types/src/codec/Compact.ts @@ -9,15 +9,11 @@ import u8aConcat from '@polkadot/util/u8a/concat'; import u8aToBn from '@polkadot/util/u8a/toBn'; import toU8a from '@polkadot/util/u8a/toU8a'; -import UInt from './UInt'; - -// TODO Could allow for 64 & 128 https://github.com/paritytech/parity-codec/pull/6 -type BitLength = 32; +import UInt, { UIntBitLength, DEFAULT_BITLENGTH } from './UInt'; const MAX_U8 = new BN(2).pow(new BN(8 - 2)).subn(1); const MAX_U16 = new BN(2).pow(new BN(16 - 2)).subn(1); const MAX_U32 = new BN(2).pow(new BN(32 - 2)).subn(1); -const DEFAULT_BITLENGTH = 32; // A new compact length-encoding algorithm. It performs the same function as Length, however // differs in that it uses a variable number of bytes to do the actual encoding. From the Rust @@ -34,8 +30,8 @@ const DEFAULT_BITLENGTH = 32; // nn nn nn 11 [ / zz zz zz zz ]{4 + n} // // Note: we use *LOW BITS* of the LSB in LE encoding to encode the 2 bit key. -export default class Compact { - static decode (_input: Uint8Array | string, bitLength: BitLength = DEFAULT_BITLENGTH): [number, BN] { +export default class Compact extends UInt { + static decodeU8a (_input: Uint8Array | string, bitLength: UIntBitLength = DEFAULT_BITLENGTH): [number, BN] { const input = toU8a(_input); const flag = input[0] & 0b11; @@ -52,24 +48,40 @@ export default class Compact { return [byteLength + 1, u8aToBn(input.subarray(1, 1 + byteLength), true)]; } - static encode (_length: UInt | BN | number, bitLength: BitLength = DEFAULT_BITLENGTH): Uint8Array { - const length = _length instanceof UInt - ? _length.toBn() - : bnToBn(_length); + static encodeU8a (_value: UInt | BN | number, bitLength: UIntBitLength = DEFAULT_BITLENGTH): Uint8Array { + const value = _value instanceof UInt + ? _value.toBn() + : bnToBn(_value); - if (length.lte(MAX_U8)) { - return new Uint8Array([length.toNumber() << 2]); - } else if (length.lte(MAX_U16)) { - return bnToU8a(length.shln(2).addn(0b01), 16, true); - } else if (length.lte(MAX_U32)) { - return bnToU8a(length.shln(2).addn(0b10), 32, true); + if (value.lte(MAX_U8)) { + return new Uint8Array([value.toNumber() << 2]); + } else if (value.lte(MAX_U16)) { + return bnToU8a(value.shln(2).addn(0b01), 16, true); + } else if (value.lte(MAX_U32)) { + return bnToU8a(value.shln(2).addn(0b10), 32, true); } return u8aConcat( new Uint8Array([ 0b11 ]), - bnToU8a(length, bitLength, true) + bnToU8a(value, bitLength, true) ); } + + byteLength (): number { + return this.toU8a().length; + } + + fromU8a (input: Uint8Array): UInt { + const [, value] = Compact.decodeU8a(input, this._bitLength); + + this.raw = value; + + return this; + } + + toU8a (isBare?: boolean): Uint8Array { + return Compact.encodeU8a(this.raw, this._bitLength); + } } diff --git a/packages/types/src/codec/UInt.ts b/packages/types/src/codec/UInt.ts index 46af7cf3924f..61d71ff24bd3 100644 --- a/packages/types/src/codec/UInt.ts +++ b/packages/types/src/codec/UInt.ts @@ -16,7 +16,9 @@ import u8aToBn from '@polkadot/util/u8a/toBn'; import Base from './Base'; -type BitLength = 8 | 16 | 32 | 64 | 128 | 256; +export type UIntBitLength = 8 | 16 | 32 | 64 | 128 | 256; + +export const DEFAULT_BITLENGTH = 64; // A generic number codec. For Substrate all numbers are LE encoded, this handles the encoding // and decoding of those numbers. Upon construction the bitLength is provided and any additional @@ -25,10 +27,10 @@ type BitLength = 8 | 16 | 32 | 64 | 128 | 256; // TODO: // - Apart from encoding/decoding we don't actuall keep check on the sizes, is this good enough? export default class UInt extends Base { - private _bitLength: BitLength; + protected _bitLength: UIntBitLength; private _isHexJson: boolean; - constructor (value: AnyNumber = 0, bitLength: BitLength = 64, isHexJson: boolean = true) { + constructor (value: AnyNumber = 0, bitLength: UIntBitLength = DEFAULT_BITLENGTH, isHexJson: boolean = true) { super( UInt.decode(value) ); diff --git a/packages/types/src/codec/Vector.ts b/packages/types/src/codec/Vector.ts index b83b32b7b3d7..2c767db1a832 100644 --- a/packages/types/src/codec/Vector.ts +++ b/packages/types/src/codec/Vector.ts @@ -48,7 +48,7 @@ export default class Vector < byteLength (): number { return this.raw.reduce((total, raw) => { return total + raw.byteLength(); - }, Compact.encode(this.length).length); + }, Compact.encodeU8a(this.length).length); } filter (fn: (item: T, index?: number) => any): Array { @@ -72,7 +72,7 @@ export default class Vector < } fromU8a (input: Uint8Array): Vector { - let [offset, _length] = Compact.decode(input); + let [offset, _length] = Compact.decodeU8a(input); const length = _length.toNumber(); this.raw = []; @@ -117,7 +117,7 @@ export default class Vector < return isBare ? u8aConcat(...encoded) : u8aConcat( - Compact.encode(this.length), + Compact.encodeU8a(this.length), ...encoded ); } diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 781c20e4d8d9..b7b0ee875f24 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -18,6 +18,11 @@ export { default as Block } from './Block'; export { default as BlockNumber } from './BlockNumber'; export { default as Bool } from './Bool'; export { default as Bytes } from './Bytes'; +export { default as Compact16 } from './Compact16'; +export { default as Compact32 } from './Compact32'; +export { default as Compact64 } from './Compact64'; +export { default as Compact128 } from './Compact128'; +export { default as Compact256 } from './Compact256'; export { default as Extrinsic } from './Extrinsic'; export { default as ExtrinsicEra } from './ExtrinsicEra'; export { default as ExtrinsicSignature } from './ExtrinsicSignature';