Skip to content

Commit

Permalink
fix: fix stablecontainer with variable fields
Browse files Browse the repository at this point in the history
  • Loading branch information
wemeetagain authored and twoeths committed Sep 19, 2024
1 parent f43c6a6 commit b356202
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 20 deletions.
35 changes: 25 additions & 10 deletions packages/ssz/src/type/stableContainer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,11 +225,10 @@ export class StableContainerType<Fields extends Record<string, Type<unknown>>> 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++) {
Expand All @@ -241,7 +240,7 @@ export class StableContainerType<Fields extends Record<string, Type<unknown>>> 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]);
Expand Down Expand Up @@ -292,11 +291,10 @@ export class StableContainerType<Fields extends Record<string, Type<unknown>>> 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);
Expand All @@ -310,7 +308,7 @@ export class StableContainerType<Fields extends Record<string, Type<unknown>>> 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);
Expand Down Expand Up @@ -501,7 +499,15 @@ export class StableContainerType<Fields extends Record<string, Type<unknown>>> e

equals(a: ValueOfFields<Fields>, b: ValueOfFields<Fields>): 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;
}
Expand Down Expand Up @@ -542,7 +548,14 @@ export class StableContainerType<Fields extends Record<string, Type<unknown>>> 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
Expand Down Expand Up @@ -588,19 +601,21 @@ function readVariableOffsets(
data: DataView,
start: number,
end: number,
activeFieldsEnd: number,
fixedEnd: number,
variableOffsetsPosition: number[]
): number[] {
// Since variable-sized values can be interspersed with fixed-sized values, we precalculate
// 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<number>(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) {
Expand Down
5 changes: 2 additions & 3 deletions packages/ssz/src/viewDU/stableContainer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,11 +143,10 @@ class ContainerTreeViewDU<Fields extends Record<string, Type<unknown>>> 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];
Expand All @@ -162,7 +161,7 @@ class ContainerTreeViewDU<Fields extends Record<string, Type<unknown>>> 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
Expand Down
100 changes: 93 additions & 7 deletions packages/ssz/test/unit/byType/stableContainer/valid.test.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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",
},
],
});

0 comments on commit b356202

Please sign in to comment.