Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions packages/pointers/src/dereference/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,49 @@ describe("dereference", () => {
});
});

it("resolves .length lookups of $this inside list items", async () => {
// mirrors the shape of the `struct Record[] memory` schema example,
// where each item's offset is computed from its own length
const pointer: Pointer = {
list: {
count: 3,
each: "item-index",
is: {
name: "item",
location: "memory",
offset: {
$sum: [64, { $product: ["item-index", { ".length": "$this" }] }],
},
length: 32,
},
},
};

const cursor = await dereference(pointer);

const { regions } = await cursor.view(state);

expect(regions.map(({ offset }) => offset!.asUint())).toEqual([
64n,
96n,
128n,
]);
});

it("throws an error on a self-referential $this lookup", async () => {
const pointer: Pointer = {
location: "memory",
offset: 0,
length: { ".length": "$this" },
};

const cursor = await dereference(pointer);

await expect(cursor.view(state)).rejects.toThrow(
"Circular reference detected: $this.length",
);
});

it("throws an error on circular reference", async () => {
const pointer: Pointer = {
location: "memory",
Expand Down
219 changes: 219 additions & 0 deletions packages/pointers/src/read.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,3 +237,222 @@ describe("read", () => {
);
});
});

describe("read (segment carry)", () => {
// each slot `n` holds 32 bytes, all equal to `0xa0 + n`, so that the
// bytes of a result identify which slot(s) they came from
const wordFor = (index: bigint): Uint8Array =>
new Uint8Array(32).fill(0xa0 + Number(index));

const sliced = (
word: Uint8Array,
slice: Machine.State.Slice | undefined,
): Data =>
Data.fromBytes(
slice
? word.slice(Number(slice.offset), Number(slice.offset + slice.length))
: word,
);

let options: ReadOptions;

beforeEach(() => {
const words: Machine.State.Words = {
read: vitest.fn(async ({ slot, slice }) =>
sliced(wordFor(slot.asUint()), slice),
),
};

const state: Machine.State = {
stack: {
length: 50n,
peek: vitest.fn(async ({ depth, slice }) =>
sliced(wordFor(depth), slice),
),
},
storage: words,
transient: {
read: vitest.fn(async ({ slot, slice }) =>
sliced(wordFor(slot.asUint()), slice),
),
},
} as unknown as Machine.State;

options = { state };
});

const bytes = (...runs: [number, number][]): Data =>
Data.fromBytes(
new Uint8Array(
runs.flatMap(([byte, count]) => new Array(count).fill(byte)),
),
);

it("treats offset $wordsize as byte 0 of the next slot", async () => {
const region: Cursor.Region<Pointer.Region.Storage> = {
location: "storage",
slot: Data.fromNumber(0),
offset: Data.fromNumber(32),
length: Data.fromNumber(4),
};

const result = await read(region, options);

expect(options.state.storage.read).toHaveBeenCalledTimes(1);
expect(options.state.storage.read).toHaveBeenCalledWith({
slot: Data.fromNumber(1),
slice: { offset: 0n, length: 4n },
});
expect(result).toEqual(bytes([0xa1, 4]));
});

it("carries an offset beyond $wordsize into a later slot", async () => {
const region: Cursor.Region<Pointer.Region.Storage> = {
location: "storage",
slot: Data.fromNumber(5),
offset: Data.fromNumber(40),
length: Data.fromNumber(8),
};

const result = await read(region, options);

expect(options.state.storage.read).toHaveBeenCalledTimes(1);
expect(options.state.storage.read).toHaveBeenCalledWith({
slot: Data.fromNumber(6),
slice: { offset: 8n, length: 8n },
});
expect(result).toEqual(bytes([0xa6, 8]));
});

it("concatenates a range that spans a slot boundary", async () => {
const region: Cursor.Region<Pointer.Region.Storage> = {
location: "storage",
slot: Data.fromNumber(0),
offset: Data.fromNumber(28),
length: Data.fromNumber(8),
};

const result = await read(region, options);

expect(options.state.storage.read).toHaveBeenCalledTimes(2);
expect(options.state.storage.read).toHaveBeenNthCalledWith(1, {
slot: Data.fromNumber(0),
slice: { offset: 28n, length: 4n },
});
expect(options.state.storage.read).toHaveBeenNthCalledWith(2, {
slot: Data.fromNumber(1),
slice: { offset: 0n, length: 4n },
});
expect(result).toEqual(bytes([0xa0, 4], [0xa1, 4]));
});

it("concatenates a range that spans three slots", async () => {
const region: Cursor.Region<Pointer.Region.Storage> = {
location: "storage",
slot: Data.fromNumber(0),
offset: Data.fromNumber(16),
length: Data.fromNumber(64),
};

const result = await read(region, options);

expect(options.state.storage.read).toHaveBeenCalledTimes(3);
expect(options.state.storage.read).toHaveBeenNthCalledWith(1, {
slot: Data.fromNumber(0),
slice: { offset: 16n, length: 16n },
});
expect(options.state.storage.read).toHaveBeenNthCalledWith(2, {
slot: Data.fromNumber(1),
slice: { offset: 0n, length: 32n },
});
expect(options.state.storage.read).toHaveBeenNthCalledWith(3, {
slot: Data.fromNumber(2),
slice: { offset: 0n, length: 16n },
});
expect(result).toEqual(bytes([0xa0, 16], [0xa1, 32], [0xa2, 16]));
});

it("defaults length to the end of the slot it begins in", async () => {
const region: Cursor.Region<Pointer.Region.Storage> = {
location: "storage",
slot: Data.fromNumber(0),
offset: Data.fromNumber(40),
};

const result = await read(region, options);

expect(options.state.storage.read).toHaveBeenCalledTimes(1);
expect(options.state.storage.read).toHaveBeenCalledWith({
slot: Data.fromNumber(1),
slice: { offset: 8n, length: 24n },
});
expect(result).toEqual(bytes([0xa1, 24]));
});

it("reads nothing for a zero-length segment", async () => {
const region: Cursor.Region<Pointer.Region.Storage> = {
location: "storage",
slot: Data.fromNumber(0),
offset: Data.fromNumber(0),
length: Data.fromNumber(0),
};

const result = await read(region, options);

expect(options.state.storage.read).not.toHaveBeenCalled();
expect(result).toEqual(Data.zero());
});

it("preserves the width of the given slot", async () => {
const slot = Data.fromHex(`0x${"00".repeat(31)}07`);
const region: Cursor.Region<Pointer.Region.Storage> = {
location: "storage",
slot,
offset: Data.fromNumber(32),
length: Data.fromNumber(1),
};

await read(region, options);

expect(options.state.storage.read).toHaveBeenCalledWith({
slot: Data.fromHex(`0x${"00".repeat(31)}08`),
slice: { offset: 0n, length: 1n },
});
});

it("applies carry to transient storage", async () => {
const region: Cursor.Region<Pointer.Region.Transient> = {
location: "transient",
slot: Data.fromNumber(0),
offset: Data.fromNumber(30),
length: Data.fromNumber(4),
};

const result = await read(region, options);

expect(options.state.transient.read).toHaveBeenCalledTimes(2);
expect(result).toEqual(bytes([0xa0, 2], [0xa1, 2]));
});

it("applies carry to stack depth", async () => {
const region: Cursor.Region<Pointer.Region.Stack> = {
location: "stack",
slot: Data.fromNumber(2),
offset: Data.fromNumber(30),
length: Data.fromNumber(4),
};

const result = await read(region, options);

expect(options.state.stack.peek).toHaveBeenCalledTimes(2);
expect(options.state.stack.peek).toHaveBeenNthCalledWith(1, {
depth: 2n,
slice: { offset: 30n, length: 2n },
});
expect(options.state.stack.peek).toHaveBeenNthCalledWith(2, {
depth: 3n,
slice: { offset: 0n, length: 2n },
});
expect(result).toEqual(bytes([0xa2, 2], [0xa3, 2]));
});
});
Loading
Loading