diff --git a/src/backings/tree/treeValue.ts b/src/backings/tree/treeValue.ts index af1a45b9..f112467a 100644 --- a/src/backings/tree/treeValue.ts +++ b/src/backings/tree/treeValue.ts @@ -14,6 +14,7 @@ import { isVectorType, isUnionType, UnionType, + isContainerLeafNodeStructType, } from "../../types"; import {byteArrayEquals} from "../../util/byteArray"; import {isTree} from "../../util/tree"; @@ -55,7 +56,11 @@ export function getTreeValueClass(type: CompositeType< return (CompositeArrayTreeValue as unknown) as TreeValueConstructor; } } else if (isContainerType(type)) { - return (ContainerTreeValue as unknown) as TreeValueConstructor; + if (isContainerLeafNodeStructType(type)) { + return (ContainerLeafNodeStructTreeValue as unknown) as TreeValueConstructor; + } else { + return (ContainerTreeValue as unknown) as TreeValueConstructor; + } } else if (isUnionType(type)) { return (UnionTreeValue as unknown) as TreeValueConstructor; } @@ -557,6 +562,75 @@ export class ContainerTreeValue extends TreeValue { } } +/** + * Custom TreeValue to be used in `ContainerLeafNodeStructType`. + * + * It skips extra work done in `ContainerTreeValue` since all data is represented as struct and should be returned + * as struct, not as TreeBacked. + */ +export class ContainerLeafNodeStructTreeValue extends TreeValue { + type: ContainerType; + + constructor(type: ContainerType, tree: Tree) { + super(type, tree); + this.type = type; + } + + getProperty

(property: P): ValueOf { + return this.type.tree_getProperty(this.tree, property) as ValueOf; + } + + setProperty

(property: P, value: ValueOf): boolean { + return this.type.tree_setProperty(this.tree, property, value); + } + + *keys(): IterableIterator { + yield* this.getPropertyNames() as string[]; + } + + *values(): IterableIterator> { + for (const [_key, value] of this.entries()) { + yield value; + } + } + + *entries(): IterableIterator<[string, ValueOf]> { + const keys = this.getPropertyNames(); + let i = 0; + for (const value of this.type.tree_iterateValues(this.tree)) { + const propName = keys[i] as keyof T; + yield [propName as string, value as ValueOf]; + i++; + } + } + + *readonlyValues(): IterableIterator> { + return yield* this.values(); + } + + *readonlyEntries(): IterableIterator<[string, ValueOf]> { + return yield* this.entries(); + } + + keysArray(): string[] { + return this.getPropertyNames() as string[]; + } + valuesArray(): ValueOf[] { + return this.type.tree_getValues(this.tree) as ValueOf[]; + } + entriesArray(): [string, ValueOf][] { + const keys = this.getPropertyNames(); + const values = this.type.tree_getValues(this.tree); + return keys.map((key, i) => [key as string, values[i] as ValueOf]); + } + readonlyValuesArray(): ValueOf[] { + return this.valuesArray(); + } + readonlyEntriesArray(): [string, ValueOf][] { + return this.entriesArray(); + } +} + export class UnionTreeValue> extends TreeValue { type: UnionType; diff --git a/src/types/composite/containerLeafNodeStruct.ts b/src/types/composite/containerLeafNodeStruct.ts index a5d262a1..2ce90c4c 100644 --- a/src/types/composite/containerLeafNodeStruct.ts +++ b/src/types/composite/containerLeafNodeStruct.ts @@ -1,9 +1,22 @@ import {CompositeValue, List, ObjectLike} from "../../interface"; import {Node, BranchNode, Tree, hashObjectToUint8Array} from "@chainsafe/persistent-merkle-tree"; -import {CompositeArrayTreeValue, TreeProxyHandler, TreeValue} from "../../backings/tree/treeValue"; -import {TreeBacked, ValueOf} from "../../backings"; +import { + CompositeArrayTreeValue, + ContainerLeafNodeStructTreeValue, + TreeProxyHandler, +} from "../../backings/tree/treeValue"; +import {TreeBacked} from "../../backings"; import {HashObject} from "@chainsafe/as-sha256"; -import {ContainerType} from "./container"; +import {ContainerType, IContainerOptions} from "./container"; +import {isTypeOf, Type} from "../type"; + +export const CONTAINER_LEAF_NODE_STRUCT_TYPE = Symbol.for("ssz/ContainerLeafNodeStructType"); + +export function isContainerLeafNodeStructType( + type: Type +): type is ContainerLeafNodeStructType { + return isTypeOf(type, CONTAINER_LEAF_NODE_STRUCT_TYPE); +} /** * Container that when represented as a Tree its children's data is represented as a struct, not a tree. @@ -13,6 +26,11 @@ import {ContainerType} from "./container"; * expensive because the tree has to be recreated every time. */ export class ContainerLeafNodeStructType extends ContainerType { + constructor(options: IContainerOptions) { + super(options); + this._typeSymbols.add(CONTAINER_LEAF_NODE_STRUCT_TYPE); + } + /** Method to allow the Node to merkelize the struct */ toFullTree(value: T): Tree { return super.struct_convertToTree(value); @@ -165,75 +183,6 @@ export class BranchNodeStruct extends Node { } } -/** - * Custom TreeValue to be used in `ContainerLeafNodeStructType`. - * - * It skips extra work done in `ContainerTreeValue` since all data is represented as struct and should be returned - * as struct, not as TreeBacked. - */ -export class ContainerLeafNodeStructTreeValue extends TreeValue { - type: ContainerType; - - constructor(type: ContainerType, tree: Tree) { - super(type, tree); - this.type = type; - } - - getProperty

(property: P): ValueOf { - return this.type.tree_getProperty(this.tree, property) as ValueOf; - } - - setProperty

(property: P, value: ValueOf): boolean { - return this.type.tree_setProperty(this.tree, property, value); - } - - *keys(): IterableIterator { - yield* this.getPropertyNames() as string[]; - } - - *values(): IterableIterator> { - for (const [_key, value] of this.entries()) { - yield value; - } - } - - *entries(): IterableIterator<[string, ValueOf]> { - const keys = this.getPropertyNames(); - let i = 0; - for (const value of this.type.tree_iterateValues(this.tree)) { - const propName = keys[i] as keyof T; - yield [propName as string, value as ValueOf]; - i++; - } - } - - *readonlyValues(): IterableIterator> { - return yield* this.values(); - } - - *readonlyEntries(): IterableIterator<[string, ValueOf]> { - return yield* this.entries(); - } - - keysArray(): string[] { - return this.getPropertyNames() as string[]; - } - valuesArray(): ValueOf[] { - return this.type.tree_getValues(this.tree) as ValueOf[]; - } - entriesArray(): [string, ValueOf][] { - const keys = this.getPropertyNames(); - const values = this.type.tree_getValues(this.tree); - return keys.map((key, i) => [key as string, values[i] as ValueOf]); - } - readonlyValuesArray(): ValueOf[] { - return this.valuesArray(); - } - readonlyEntriesArray(): [string, ValueOf][] { - return this.entriesArray(); - } -} - /** * Custom readonlyValues to return non-tree backed values, but the raw struct inside BranchNodeStruct nodes. * diff --git a/test/unit/validators.test.ts b/test/unit/validators.test.ts index 22d0f911..029f72d2 100644 --- a/test/unit/validators.test.ts +++ b/test/unit/validators.test.ts @@ -120,5 +120,19 @@ describe("Container with BranchNodeStruct", function () { expect(readonlyValuesListOfLeafNodeStruct(validatorListTB)).to.deep.equal(validatorsFlat); }); + + it("return each property as struct backed", () => { + const validatorListTB = ValidtorsListType.defaultTreeBacked(); + validatorListTB.push(validator); + for (const key of Object.keys(validator) as (keyof typeof validator)[]) { + expect(validatorListTB[0][key].valueOf()).to.deep.equal(validator[key], `wrong ${key} value`); + } + }); + + it("validator.valueOf()", () => { + const validatorListTB = ValidtorsListType.defaultTreeBacked(); + validatorListTB.push(validator); + expect(validatorListTB[0].valueOf()).to.deep.equal(validator); + }); }); });