From b356202715dd053dd6868e770686a5be835acf6c Mon Sep 17 00:00:00 2001 From: Cayman Date: Tue, 14 May 2024 10:08:41 -0400 Subject: [PATCH] fix: fix stablecontainer with variable fields --- packages/ssz/src/type/stableContainer.ts | 35 ++++-- packages/ssz/src/viewDU/stableContainer.ts | 5 +- .../unit/byType/stableContainer/valid.test.ts | 100 ++++++++++++++++-- 3 files changed, 120 insertions(+), 20 deletions(-) diff --git a/packages/ssz/src/type/stableContainer.ts b/packages/ssz/src/type/stableContainer.ts index c5810e8e..6d502fc7 100644 --- a/packages/ssz/src/type/stableContainer.ts +++ b/packages/ssz/src/type/stableContainer.ts @@ -225,11 +225,10 @@ export class StableContainerType>> e const activeFields = BitArray.fromBoolArray(this.fieldsEntries.map(({fieldName}) => value[fieldName] != null)); // write active field bitvector output.uint8Array.set(activeFields.uint8Array, offset); - offset += activeFields.uint8Array.length; const {fixedEnd} = computeSerdesData(activeFields, this.fieldsEntries); - let fixedIndex = offset; + let fixedIndex = offset + activeFields.uint8Array.length; let variableIndex = offset + fixedEnd; for (let i = 0; i < this.fieldsEntries.length; i++) { @@ -241,7 +240,7 @@ export class StableContainerType>> e if (fieldType.fixedSize === null) { // write offset - output.dataView.setUint32(fixedIndex, variableIndex - offset, true); + output.dataView.setUint32(fixedIndex, variableIndex - activeFields.uint8Array.length - offset, true); fixedIndex += 4; // write serialized element to variable section variableIndex = fieldType.value_serializeToBytes(output, variableIndex, value[fieldName]); @@ -292,11 +291,10 @@ export class StableContainerType>> e const activeFields = this.tree_getActiveFields(node); // write active field bitvector output.uint8Array.set(activeFields.uint8Array, offset); - offset += activeFields.uint8Array.length; const {fixedEnd} = computeSerdesData(activeFields, this.fieldsEntries); - let fixedIndex = offset; + let fixedIndex = offset + activeFields.uint8Array.length; let variableIndex = offset + fixedEnd; const nodes = getNodesAtDepth(node, this.depth, 0, this.fieldsEntries.length); @@ -310,7 +308,7 @@ export class StableContainerType>> e const node = nodes[i]; if (fieldType.fixedSize === null) { // write offset - output.dataView.setUint32(fixedIndex, variableIndex - offset, true); + output.dataView.setUint32(fixedIndex, variableIndex - activeFields.uint8Array.length - offset, true); fixedIndex += 4; // write serialized element to variable section variableIndex = fieldType.tree_serializeToBytes(output, variableIndex, node); @@ -501,7 +499,15 @@ export class StableContainerType>> e equals(a: ValueOfFields, b: ValueOfFields): boolean { for (let i = 0; i < this.fieldsEntries.length; i++) { - const {fieldName, fieldType} = this.fieldsEntries[i]; + const {fieldName, fieldType, optional} = this.fieldsEntries[i]; + if (optional) { + if (a[fieldName] == null && b[fieldName] == null) { + continue; + } + if (a[fieldName] == null || b[fieldName] == null) { + return false; + } + } if (!fieldType.equals(a[fieldName], b[fieldName])) { return false; } @@ -542,7 +548,14 @@ export class StableContainerType>> e } // Read offsets in one pass - const offsets = readVariableOffsets(data.dataView, start, end, fixedEnd, variableOffsetsPosition); + const offsets = readVariableOffsets( + data.dataView, + start, + end, + activeFieldsByteLen, + fixedEnd, + variableOffsetsPosition + ); offsets.push(end - start); // The offsets are relative to the start // Merge fieldRangesFixedLen + offsets in one array @@ -588,6 +601,7 @@ function readVariableOffsets( data: DataView, start: number, end: number, + activeFieldsEnd: number, fixedEnd: number, variableOffsetsPosition: number[] ): number[] { @@ -595,12 +609,13 @@ function readVariableOffsets( // the offset indices so we can more easily deserialize the fields in once pass first we get the fixed sizes // Note: `fixedSizes[i] = null` if that field has variable length - const size = end - start; + const size = end - activeFieldsEnd; + const activeFieldsByteLen = activeFieldsEnd - start; // with the fixed sizes, we can read the offsets, and store for our single pass const offsets = new Array(variableOffsetsPosition.length); for (let i = 0; i < variableOffsetsPosition.length; i++) { - const offset = data.getUint32(start + variableOffsetsPosition[i], true); + const offset = data.getUint32(start + variableOffsetsPosition[i], true) + activeFieldsByteLen; // Validate offsets. If the list is empty the offset points to the end of the buffer, offset == size if (offset > size) { diff --git a/packages/ssz/src/viewDU/stableContainer.ts b/packages/ssz/src/viewDU/stableContainer.ts index 7821029e..3c04a78c 100644 --- a/packages/ssz/src/viewDU/stableContainer.ts +++ b/packages/ssz/src/viewDU/stableContainer.ts @@ -143,11 +143,10 @@ class ContainerTreeViewDU>> extends const activeFields = this.type.tree_getActiveFields(this.node); // write active fields bitvector output.uint8Array.set(activeFields.uint8Array, offset); - offset += activeFields.uint8Array.length; const {fixedEnd} = computeSerdesData(activeFields, this.type.fieldsEntries); - let fixedIndex = offset; + let fixedIndex = offset + activeFields.uint8Array.length; let variableIndex = offset + fixedEnd; for (let index = 0; index < this.type.fieldsEntries.length; index++) { const {fieldType, optional} = this.type.fieldsEntries[index]; @@ -162,7 +161,7 @@ class ContainerTreeViewDU>> extends } if (fieldType.fixedSize === null) { // write offset - output.dataView.setUint32(fixedIndex, variableIndex - offset, true); + output.dataView.setUint32(fixedIndex, variableIndex - activeFields.uint8Array.length - offset, true); fixedIndex += 4; // write serialized element to variable section // basic types always have fixedSize diff --git a/packages/ssz/test/unit/byType/stableContainer/valid.test.ts b/packages/ssz/test/unit/byType/stableContainer/valid.test.ts index 863d8a65..4b905946 100644 --- a/packages/ssz/test/unit/byType/stableContainer/valid.test.ts +++ b/packages/ssz/test/unit/byType/stableContainer/valid.test.ts @@ -1,11 +1,11 @@ -import {OptionalType, StableContainerType, UintNumberType} from "../../../../src"; +import {ListBasicType, OptionalType, StableContainerType, UintNumberType} from "../../../../src"; import {runTypeTestValid} from "../runTypeTestValid"; // taken from eip spec tests const optionalUint16 = new OptionalType(new UintNumberType(2)); const byteType = new UintNumberType(1); -const Shape = new StableContainerType( +const Shape1 = new StableContainerType( { side: optionalUint16, color: byteType, @@ -14,33 +14,119 @@ const Shape = new StableContainerType( 4 ); +const Shape2 = new StableContainerType( + { + side: optionalUint16, + color: byteType, + radius: optionalUint16, + }, + 8 +); + +const Shape3 = new StableContainerType( + { + side: optionalUint16, + colors: new OptionalType(new ListBasicType(byteType, 4)), + radius: optionalUint16, + }, + 8 +); + runTypeTestValid({ - type: Shape, + type: Shape1, defaultValue: {side: null, color: 0, radius: null}, values: [ { - id: "shape-0", + id: "shape1-0", serialized: "0x074200014200", json: {side: 0x42, color: 1, radius: 0x42}, root: "0x37b28eab19bc3e246e55d2e2b2027479454c27ee006d92d4847c84893a162e6d", }, { - id: "shape-1", + id: "shape1-1", serialized: "0x03420001", json: {side: 0x42, color: 1, radius: null}, root: "0xbfdb6fda9d02805e640c0f5767b8d1bb9ff4211498a5e2d7c0f36e1b88ce57ff", }, { - id: "shape-2", + id: "shape1-2", serialized: "0x0201", json: {side: null, color: 1, radius: null}, root: "0x522edd7309c0041b8eb6a218d756af558e9cf4c816441ec7e6eef42dfa47bb98", }, { - id: "shape-3", + id: "shape1-3", serialized: "0x06014200", json: {side: null, color: 1, radius: 0x42}, root: "0xf66d2c38c8d2afbd409e86c529dff728e9a4208215ca20ee44e49c3d11e145d8", }, ], }); +// +runTypeTestValid({ + type: Shape2, + defaultValue: {side: null, color: 0, radius: null}, + values: [ + { + id: "shape2-0", + serialized: "0x074200014200", + json: {side: 0x42, color: 1, radius: 0x42}, + root: "0x0792fb509377ee2ff3b953dd9a88eee11ac7566a8df41c6c67a85bc0b53efa4e", + }, + { + id: "shape2-1", + serialized: "0x03420001", + json: {side: 0x42, color: 1, radius: null}, + root: "0xddc7acd38ae9d6d6788c14bd7635aeb1d7694768d7e00e1795bb6d328ec14f28", + }, + { + id: "shape2-2", + serialized: "0x0201", + json: {side: null, color: 1, radius: null}, + root: "0x9893ecf9b68030ff23c667a5f2e4a76538a8e2ab48fd060a524888a66fb938c9", + }, + { + id: "shape2-3", + serialized: "0x06014200", + json: {side: null, color: 1, radius: 0x42}, + root: "0xe823471310312d52aa1135d971a3ed72ba041ade3ec5b5077c17a39d73ab17c5", + }, + ], +}); + +runTypeTestValid({ + type: Shape3, + defaultValue: {side: null, colors: null, radius: null}, + values: [ + { + id: "shape2-0", + serialized: "0x0742000800000042000102", + json: {side: 0x42, colors: [1, 2], radius: 0x42}, + root: "0x1093b0f1d88b1b2b458196fa860e0df7a7dc1837fe804b95d664279635cb302f", + }, + { + id: "shape2-1", + serialized: "0x014200", + json: {side: 0x42, colors: null, radius: null}, + root: "0x28df3f1c3eebd92504401b155c5cfe2f01c0604889e46ed3d22a3091dde1371f", + }, + { + id: "shape2-2", + serialized: "0x02040000000102", + json: {side: null, colors: [1, 2], radius: null}, + root: "0x659638368467b2c052ca698fcb65902e9b42ce8e94e1f794dd5296ceac2dec3e", + }, + { + id: "shape2-3", + serialized: "0x044200", + json: {side: null, colors: null, radius: 0x42}, + root: "0xd585dd0561c718bf4c29e4c1bd7d4efd4a5fe3c45942a7f778acb78fd0b2a4d2", + }, + { + id: "shape2-4", + serialized: "0x060600000042000102", + json: {side: null, colors: [1, 2], radius: 0x42}, + root: "0x00fc0cecc200a415a07372d5d5b8bc7ce49f52504ed3da0336f80a26d811c7bf", + }, + ], +});