Add BTreeSet<V> Codec (#1639)

* add BTreeSet Codec

* Add following BTreeMap

* fix linting

* fix linting

* add validate Types and encode Types

* type def info is incremented.

* fix compareSet
This commit is contained in:
satellitex
2019-12-18 16:22:54 +01:00
committed by Jaco Greeff
parent 0a0f110c42
commit 53959d482d
21 changed files with 2144 additions and 1805 deletions
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,49 +1,49 @@
[
{
"info": 7,
"info": 8,
"type": "{\"value\": {\"cell\": {\"cell\": ([u8;32])}}}",
"name": "Flipper",
"namespace": "flipper::normal",
"sub": [
{
"info": 7,
"info": 8,
"type": "{\"cell\": {\"cell\": ([u8;32])}}",
"name": "value",
"namespace": "ink_core::storage::value",
"params": [
{
"info": 5,
"info": 6,
"type": "bool"
}
],
"sub": [
{
"info": 7,
"info": 8,
"type": "{\"cell\": ([u8;32])}",
"name": "cell",
"namespace": "ink_core::storage::cell::sync_cell",
"params": [
{
"info": 5,
"info": 6,
"type": "bool"
}
],
"sub": [
{
"info": 8,
"info": 9,
"type": "([u8;32])",
"name": "cell",
"namespace": "ink_core::storage::key",
"sub": [
{
"info": 10,
"info": 11,
"type": "[u8;32]",
"ext": {
"length": 32,
"type": ""
},
"sub": {
"info": 5,
"info": 6,
"type": "u8"
},
"index": 0
@@ -57,44 +57,44 @@
]
},
{
"info": 7,
"info": 8,
"type": "{\"cell\": {\"cell\": ([u8;32])}}",
"name": "Value",
"namespace": "ink_core::storage::value",
"params": [
{
"info": 5,
"info": 6,
"type": "bool"
}
],
"sub": [
{
"info": 7,
"info": 8,
"type": "{\"cell\": ([u8;32])}",
"name": "cell",
"namespace": "ink_core::storage::cell::sync_cell",
"params": [
{
"info": 5,
"info": 6,
"type": "bool"
}
],
"sub": [
{
"info": 8,
"info": 9,
"type": "([u8;32])",
"name": "cell",
"namespace": "ink_core::storage::key",
"sub": [
{
"info": 10,
"info": 11,
"type": "[u8;32]",
"ext": {
"length": 32,
"type": ""
},
"sub": {
"info": 5,
"info": 6,
"type": "u8"
},
"index": 0
@@ -106,36 +106,36 @@
]
},
{
"info": 5,
"info": 6,
"type": "bool"
},
{
"info": 7,
"info": 8,
"type": "{\"cell\": ([u8;32])}",
"name": "SyncCell",
"namespace": "ink_core::storage::cell::sync_cell",
"params": [
{
"info": 5,
"info": 6,
"type": "bool"
}
],
"sub": [
{
"info": 8,
"info": 9,
"type": "([u8;32])",
"name": "cell",
"namespace": "ink_core::storage::key",
"sub": [
{
"info": 10,
"info": 11,
"type": "[u8;32]",
"ext": {
"length": 32,
"type": ""
},
"sub": {
"info": 5,
"info": 6,
"type": "u8"
},
"index": 0
@@ -145,20 +145,20 @@
]
},
{
"info": 8,
"info": 9,
"type": "([u8;32])",
"name": "Key",
"namespace": "ink_core::storage::key",
"sub": [
{
"info": 10,
"info": 11,
"type": "[u8;32]",
"ext": {
"length": 32,
"type": ""
},
"sub": {
"info": 5,
"info": 6,
"type": "u8"
},
"index": 0
@@ -166,19 +166,19 @@
]
},
{
"info": 10,
"info": 11,
"type": "[u8;32]",
"ext": {
"length": 32,
"type": ""
},
"sub": {
"info": 5,
"info": 6,
"type": "u8"
}
},
{
"info": 5,
"info": 6,
"type": "u8"
}
]
@@ -1,29 +1,29 @@
[
{
"info": 9,
"info": 10,
"type": "{ \"value\": \"{ \"cell\": \"{ \"cell\": \"([u8;32])\"} }<u32>\"} }<u32>\"} }",
"sub": [
{
"info": 9,
"info": 10,
"type": "{ \"cell\": \"{ \"cell\": \"([u8;32])\"} }<u32>\"} }",
"sub": [
{
"info": 9,
"info": 10,
"type": "{ \"cell\": \"([u8;32])\"} }",
"sub": [
{
"info": 10,
"info": 11,
"type": "([u8;32])",
"sub": [
{
"info": 12,
"info": 13,
"type": "[u8;32]",
"ext": {
"length": 32,
"type": "u8"
},
"sub": {
"info": 6,
"info": 7,
"type": "u8"
},
"index": 0
@@ -37,7 +37,7 @@
"namespace": "ink_core::storage::cell::sync_cell",
"params": [
{
"info": 6,
"info": 7,
"type": "u32"
}
]
@@ -47,7 +47,7 @@
"namespace": "ink_core::storage::value",
"params": [
{
"info": 6,
"info": 7,
"type": "u32"
}
]
@@ -57,26 +57,26 @@
"namespace": "incrementer::normal"
},
{
"info": 9,
"info": 10,
"type": "{ \"cell\": \"{ \"cell\": \"([u8;32])\"} }<u32>\"} }",
"sub": [
{
"info": 9,
"info": 10,
"type": "{ \"cell\": \"([u8;32])\"} }",
"sub": [
{
"info": 10,
"info": 11,
"type": "([u8;32])",
"sub": [
{
"info": 12,
"info": 13,
"type": "[u8;32]",
"ext": {
"length": 32,
"type": "u8"
},
"sub": {
"info": 6,
"info": 7,
"type": "u8"
},
"index": 0
@@ -90,7 +90,7 @@
"namespace": "ink_core::storage::cell::sync_cell",
"params": [
{
"info": 6,
"info": 7,
"type": "u32"
}
]
@@ -100,32 +100,32 @@
"namespace": "ink_core::storage::value",
"params": [
{
"info": 6,
"info": 7,
"type": "u32"
}
]
},
{
"info": 6,
"info": 7,
"type": "u32"
},
{
"info": 9,
"info": 10,
"type": "{ \"cell\": \"([u8;32])\"} }",
"sub": [
{
"info": 10,
"info": 11,
"type": "([u8;32])",
"sub": [
{
"info": 12,
"info": 13,
"type": "[u8;32]",
"ext": {
"length": 32,
"type": "u8"
},
"sub": {
"info": 6,
"info": 7,
"type": "u8"
},
"index": 0
@@ -139,24 +139,24 @@
"namespace": "ink_core::storage::cell::sync_cell",
"params": [
{
"info": 6,
"info": 7,
"type": "u32"
}
]
},
{
"info": 10,
"info": 11,
"type": "([u8;32])",
"sub": [
{
"info": 12,
"info": 13,
"type": "[u8;32]",
"ext": {
"length": 32,
"type": "u8"
},
"sub": {
"info": 6,
"info": 7,
"type": "u8"
},
"index": 0
@@ -166,23 +166,23 @@
"namespace": "ink_core::storage::key"
},
{
"info": 12,
"info": 13,
"type": "[u8;32]",
"ext": {
"length": 32,
"type": "u8"
},
"sub": {
"info": 6,
"info": 7,
"type": "u8"
}
},
{
"info": 6,
"info": 7,
"type": "u8"
},
{
"info": 6,
"info": 7,
"type": "bool"
}
]
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -26,6 +26,7 @@ function extractTypes (types: string[]): any[] {
return extractTypes([(decoded.ext as TypeDefExtVecFixed).type]);
case TypeDefInfo.BTreeMap:
case TypeDefInfo.BTreeSet:
case TypeDefInfo.Result:
case TypeDefInfo.Tuple:
return extractTypes((decoded.sub as TypeDef[]).map((sub): string => sub.type));
+94
View File
@@ -0,0 +1,94 @@
// Copyright 2017-2019 @polkadot/types authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { TypeRegistry } from '../codec';
import Text from '../primitive/Text';
import U32 from '../primitive/U32';
import Struct from './Struct';
import { CodecTo } from '../types';
import BTreeSet from './BTreeSet';
const registry = new TypeRegistry();
const mockU32Set = new Set<U32>();
mockU32Set.add(new U32(registry, 2));
mockU32Set.add(new U32(registry, 24));
mockU32Set.add(new U32(registry, 30));
mockU32Set.add(new U32(registry, 80));
const mockU32SetString = '[2,24,30,80]';
const mockU32SetObject = [2, 24, 30, 80];
const mockU32SetHexString = '0x1002000000180000001e00000050000000';
const mockU32SetUint8Array = Uint8Array.from([16, 2, 0, 0, 0, 24, 0, 0, 0, 30, 0, 0, 0, 80, 0, 0, 0]);
describe('BTreeSet', (): void => {
describe('decoding', (): void => {
const testDecode = (type: string, input: any, output: string): void =>
it(`can decode from ${type}`, (): void => {
const s = new BTreeSet(registry, U32, input);
expect(s.toString()).toBe(output);
});
testDecode('Set', mockU32Set, mockU32SetString);
testDecode('hex', mockU32SetHexString, mockU32SetString);
testDecode('Uint8Array', mockU32SetUint8Array, mockU32SetString);
testDecode('Set', mockU32Set, mockU32SetString);
testDecode('hex', mockU32SetHexString, mockU32SetString);
testDecode('Uint8Array', mockU32SetUint8Array, mockU32SetString);
});
describe('encoding muple values', (): void => {
const testEncode = (to: CodecTo, expected: any): void =>
it(`can encode ${to}`, (): void => {
const s = new BTreeSet(registry, U32, mockU32Set);
expect(s[to]()).toEqual(expected);
});
testEncode('toHex', mockU32SetHexString);
testEncode('toJSON', mockU32SetObject);
testEncode('toU8a', mockU32SetUint8Array);
testEncode('toString', mockU32SetString);
});
it('decodes null', (): void => {
expect(
new (
BTreeSet.with(U32)
)(registry, null).toString()
).toEqual('[]');
});
it('decodes within more complicated types', (): void => {
const s = new Struct(registry, {
placeholder: U32,
value: BTreeSet.with(U32)
});
s.set('value', new BTreeSet(registry, U32, mockU32Set));
expect(s.toString()).toBe('{"placeholder":0,"value":[2,24,30,80]}');
});
it('throws when it cannot decode', (): void => {
expect(
(): BTreeSet<U32> => new (
BTreeSet.with(U32)
)(registry, 'ABC')
).toThrowError(/BTreeSet: cannot decode type/);
});
it('correctly encodes length', (): void => {
expect(
new (
BTreeSet.with(U32))(registry, mockU32Set).encodedLength
).toEqual(17);
});
it('generates sane toRawTypes', (): void => {
expect(new (BTreeSet.with(U32))(registry).toRawType()).toBe('BTreeSet<u32>');
expect(new (BTreeSet.with(Text))(registry).toRawType()).toBe('BTreeSet<Text>');
expect(new (BTreeSet.with(Struct.with({ a: U32, b: Text })))(registry).toRawType())
.toBe('BTreeSet<{"a":"u32","b":"Text"}>');
});
});
+182
View File
@@ -0,0 +1,182 @@
// Copyright 2017-2019 @polkadot/types authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { AnyJsonArray, Constructor, Codec, IHash, InterfaceTypes, Registry } from '../types';
import { isHex, hexToU8a, isU8a, u8aConcat, u8aToHex, u8aToU8a } from '@polkadot/util';
import { blake2AsU8a } from '@polkadot/util-crypto';
import Compact from './Compact';
import U8a from './U8aFixed';
import { compareSet, decodeU8a, typeToConstructor } from './utils';
export default class BTreeSet<V extends Codec = Codec> extends Set<V> implements Codec {
public readonly registry: Registry;
protected _ValClass: Constructor<V>;
constructor (registry: Registry, valType: Constructor<V> | InterfaceTypes, rawValue: any) {
const ValClass = typeToConstructor(registry, valType);
super(BTreeSet.decodeBTreeSet(registry, ValClass, rawValue));
this.registry = registry;
this._ValClass = ValClass;
}
/**
* Decode input to pass into constructor.
*
* @param ValClass - Type of the map value
* @param value - Value to decode, one of:
* - null
* - undefined
* - hex
* - Uint8Array
* - Set<any>, where both key and value types are either
* constructors or decodeable values for their types.
* @param jsonSet
*/
private static decodeBTreeSet<V extends Codec = Codec> (registry: Registry, ValClass: Constructor<V>, value: Uint8Array | string | Set<any>): Set<V> {
if (!value) {
return new Set<V>();
} else if (isHex(value)) {
return BTreeSet.decodeBTreeSet(registry, ValClass, hexToU8a(value));
} else if (isU8a(value)) {
return BTreeSet.decodeBTreeSetFromU8a<V>(registry, ValClass, u8aToU8a(value));
} else if (value instanceof Set) {
return BTreeSet.decodeBTreeSetFromSet<V>(registry, ValClass, value);
}
throw new Error('BTreeSet: cannot decode type');
}
private static decodeBTreeSetFromU8a<V extends Codec = Codec> (registry: Registry, ValClass: Constructor<V>, u8a: Uint8Array): Set<V> {
const output = new Set<V>();
const [offset, length] = Compact.decodeU8a(u8a);
const types = [];
for (let i = 0; i < length.toNumber(); i++) {
types.push(ValClass);
}
const values = decodeU8a(registry, u8a.subarray(offset), types);
for (let i = 0; i < values.length; i++) {
output.add(values[i] as V);
}
return output;
}
private static decodeBTreeSetFromSet<V extends Codec = Codec> (registry: Registry, ValClass: Constructor<V>, value: Set<any>): Set<V> {
const output = new Set<V>();
value.forEach((v: any) => {
let val;
try {
val = (v instanceof ValClass) ? v : new ValClass(registry, v);
} catch (error) {
console.error('Failed to decode BTreeSet key or value:', error.message);
throw error;
}
output.add(val);
});
return output;
}
public static with<V extends Codec> (valType: Constructor<V> | InterfaceTypes): Constructor<BTreeSet<V>> {
return class extends BTreeSet<V> {
constructor (registry: Registry, value?: any) {
super(registry, valType, value);
}
};
}
/**
* @description The length of the value when encoded as a Uint8Array
*/
public get encodedLength (): number {
let len = Compact.encodeU8a(this.size).length;
this.forEach((v: V) => {
len += v.encodedLength;
});
return len;
}
/**
* @description Returns a hash of the value
*/
public get hash (): IHash {
return new U8a(this.registry, blake2AsU8a(this.toU8a(), 256));
}
/**
* @description Checks if the value is an empty value
*/
public get isEmpty (): boolean {
return this.size === 0;
}
/**
* @description Compares the value of the input to see if there is a match
*/
public eq (other?: any): boolean {
return compareSet(this, other);
}
/**
* @description Returns a hex string representation of the value. isLe returns a LE (number-only) representation
*/
public toHex (): string {
return u8aToHex(this.toU8a());
}
/**
* @description Converts the Object to JSON, typically used for RPC transfers
*/
public toJSON (): AnyJsonArray {
const json: any = [];
this.forEach((v: V) => {
json.push(v.toJSON());
});
return json;
}
/**
* @description Returns the base runtime type name for this instance
*/
public toRawType (): string {
return `BTreeSet<${new this._ValClass(this.registry).toRawType()}>`;
}
/**
* @description Returns the string representation of the value
*/
public toString (): string {
return JSON.stringify(this.toJSON());
}
/**
* @description Encodes the value as a Uint8Array as per the SCALE specifications
* @param isBare true when the value has none of the type-specific prefixes (internal)
*/
public toU8a (isBare?: boolean): Uint8Array {
const encoded = new Array<Uint8Array>();
if (!isBare) {
encoded.push(Compact.encodeU8a(this.size));
}
this.forEach((v: V) => {
encoded.push(v.toU8a(isBare));
});
return u8aConcat(...encoded);
}
}
@@ -26,6 +26,12 @@ describe('createType', (): void => {
).toEqual('{"bazzing":69}');
});
it('allows creation of a BTreeSet', (): void => {
expect(
createTypeUnsafe(registry, 'BTreeSet<u32>', ['0x1002000000180000001e00000050000000']).toString()
).toEqual('[2,24,30,80]');
});
it('allows creation of a Result', (): void => {
expect(
createTypeUnsafe(registry, 'Result<u32,Text>', ['0x011064656667']).toJSON()
@@ -8,6 +8,7 @@ import { TypeDef, TypeDefExtVecFixed, TypeDefInfo } from './types';
import { assert } from '@polkadot/util';
import BTreeMap from '../BTreeMap';
import BTreeSet from '../BTreeSet';
import Compact from '../Compact';
import Enum from '../Enum';
import Option from '../Option';
@@ -62,6 +63,8 @@ const infoMapping: Record<TypeDefInfo, (registry: Registry, value: TypeDef) => C
return BTreeMap.with(keyType, valueType);
},
[TypeDefInfo.BTreeSet]: (registry: Registry, value: TypeDef): Constructor => BTreeSet.with(getSubType(value)),
[TypeDefInfo.Compact]: (registry: Registry, value: TypeDef): Constructor => Compact.with(getSubType(value)),
[TypeDefInfo.Enum]: (registry: Registry, value: TypeDef): Constructor => Enum.with(getTypeClassMap(value)),
@@ -47,6 +47,19 @@ describe('getTypeDef', (): void => {
});
});
it('properly decodes a BTreeSet<Text>', (): void => {
expect(
getTypeDef('BTreeSet<Text>')
).toEqual({
info: TypeDefInfo.BTreeSet,
type: 'BTreeSet<Text>',
sub: {
info: TypeDefInfo.Plain,
type: 'Text'
}
});
});
it('properly decodes a Result<u32, Text>', (): void => {
expect(
getTypeDef('Result<u32, Text>')
@@ -108,6 +108,7 @@ const nestedExtraction: [string, string, TypeDefInfo, (value: TypeDef, type: str
];
const wrappedExtraction: [string, string, TypeDefInfo][] = [
['BTreeSet<', '>', TypeDefInfo.BTreeSet],
['Compact<', '>', TypeDefInfo.Compact],
['Linkage<', '>', TypeDefInfo.Linkage],
['Option<', '>', TypeDefInfo.Option],
+1 -1
View File
@@ -5,7 +5,7 @@
type Mapper = (value: string) => string;
const ALLOWED_BOXES = ['BTreeMap', 'Compact', 'Linkage', 'Result', 'Option', 'Vec'];
const ALLOWED_BOXES = ['BTreeMap', 'BTreeSet', 'Compact', 'Linkage', 'Result', 'Option', 'Vec'];
const mappings: Mapper[] = [
// alias <T::InherentOfflineReport as InherentOfflineReport>::Inherent -> InherentOfflineReport
+1
View File
@@ -13,6 +13,7 @@ export type FromReg<T extends Codec, K extends string> = K extends InterfaceType
export enum TypeDefInfo {
BTreeMap,
BTreeSet,
Compact,
Enum,
Linkage,
+1
View File
@@ -9,6 +9,7 @@
// These are the base codec types, generally used for construction
export { default as BTreeMap } from './BTreeMap';
export { default as BTreeSet } from './BTreeSet';
export { default as Compact } from './Compact';
// export { default as Date } from './Date';
export { default as Enum } from './Enum';
@@ -0,0 +1,20 @@
// Copyright 2017-2019 @polkadot/types authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { isObject } from '@polkadot/util';
// NOTE These are used internally and when comparing objects, expects that
// when the second is an Set<string, Codec> that the first has to be as well
export default function compareSet (a: Set<any>, b?: any): boolean {
if (Array.isArray(b)) {
// equal number of entries and each entry in the array should match
return (a.size === b.length) && !b.some((entry): boolean => !a.has(entry));
} else if (b instanceof Set) {
return compareSet(a, [...b.entries()]);
} else if (isObject(b)) {
return compareSet(a, Object.entries(b));
}
return false;
}
@@ -25,6 +25,7 @@ function encodeWithParams (typeDef: Pick<TypeDef, any>, outer = typeDef.displayN
switch (info) {
case TypeDefInfo.BTreeMap:
case TypeDefInfo.BTreeSet:
case TypeDefInfo.Compact:
case TypeDefInfo.Linkage:
case TypeDefInfo.Option:
@@ -114,6 +115,7 @@ function encodeVecFixed (typeDef: Pick<TypeDef, any>): string {
// in a compile-time error with the missing index)
const encoders: Record<TypeDefInfo, (typeDef: TypeDef) => string> = {
[TypeDefInfo.BTreeMap]: (typeDef: TypeDef): string => encodeWithParams(typeDef, 'BTreeMap'),
[TypeDefInfo.BTreeSet]: (typeDef: TypeDef): string => encodeWithParams(typeDef, 'BTreeSet'),
[TypeDefInfo.Compact]: (typeDef: TypeDef): string => encodeWithParams(typeDef, 'Compact'),
[TypeDefInfo.Enum]: (typeDef: TypeDef): string => encodeEnum(typeDef),
[TypeDefInfo.Linkage]: (typeDef: TypeDef): string => encodeWithParams(typeDef, 'Linkage'),
+1
View File
@@ -4,6 +4,7 @@
export { default as compareArray } from './compareArray';
export { default as compareMap } from './compareMap';
export { default as compareSet } from './compareSet';
export { default as decodeU8a } from './decodeU8a';
export * from './encodeTypes';
export * from './mapToTypeMap';
@@ -32,6 +32,7 @@ function tsExport (definitions: object, def: TypeDef, imports: TypeImports): str
}
const tsBTreeMap = tsExport;
const tsBTreeSet = tsExport;
const tsCompact = tsExport;
const tsOption = tsExport;
const tsPlain = tsExport;
@@ -132,6 +133,7 @@ function generateInterfaces (definitions: object, { types }: { types: Record<str
// we are being explicit in having no handlers where we do not support (yet)
const generators = {
[TypeDefInfo.BTreeMap]: tsBTreeMap,
[TypeDefInfo.BTreeSet]: tsBTreeSet,
[TypeDefInfo.Compact]: tsCompact,
[TypeDefInfo.Enum]: tsEnum,
[TypeDefInfo.Linkage]: errorUnhandled,
@@ -41,6 +41,13 @@ function formatBTreeMap (key: string, val: string): string {
return `BTreeMap<${key}, ${val}>`;
}
/**
* Given the inner `V`, return a `BTreeSet<V>` string
*/
function formatBTreeSet (val: string): string {
return `BTreeSet<${val}>`;
}
/**
* Given the inner `T`, return a `Compact<T>` string
*/
@@ -129,6 +136,11 @@ export function formatType (definitions: object, type: string | TypeDef, imports
const [keyDef, valDef] = (typeDef.sub as TypeDef[]);
return formatBTreeMap(formatType(definitions, keyDef.type, imports), formatType(definitions, valDef.type, imports));
}
case TypeDefInfo.BTreeSet: {
setImports(definitions, imports, ['BTreeSet']);
const valDef = typeDef.sub as TypeDef;
return formatBTreeSet(formatType(definitions, valDef.type, imports));
}
case TypeDefInfo.Result: {
setImports(definitions, imports, ['Result']);
const [okDef, errorDef] = (typeDef.sub as TypeDef[]);