Skip to content

Commit

Permalink
Merge pull request #103 from ethdebug/pointer-templates
Browse files Browse the repository at this point in the history
Define pointer templates
  • Loading branch information
gnidan authored Sep 28, 2024
2 parents e2aab79 + 788e297 commit fd9e14c
Show file tree
Hide file tree
Showing 12 changed files with 228 additions and 21 deletions.
3 changes: 3 additions & 0 deletions packages/pointers/src/dereference/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { processPointer, type ProcessOptions } from "./process.js";
* for a particular pointer at runtime.
*/
export interface GenerateRegionsOptions {
templates: Pointer.Templates;
state: Machine.State;
initialStackLength: bigint;
}
Expand Down Expand Up @@ -62,6 +63,7 @@ export async function* generateRegions(
}

async function initializeProcessOptions({
templates,
state,
initialStackLength
}: GenerateRegionsOptions): Promise<ProcessOptions> {
Expand All @@ -72,6 +74,7 @@ async function initializeProcessOptions({
const variables: Record<string, Data> = {};

return {
templates,
state,
stackLengthChange,
regions,
Expand Down
31 changes: 31 additions & 0 deletions packages/pointers/src/dereference/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -250,4 +250,35 @@ describe("dereference", () => {
expect(regions[0].offset).toEqual(Data.fromNumber(0));
expect(regions[0].length).toEqual(Data.fromNumber(32));
});

it("works for templates", async () => {
const templates: Pointer.Templates = {
"memory-range": {
expect: ["offset", "length"],
for: {
location: "memory",
offset: "offset",
length: "length"
}
}
};

const pointer: Pointer = {
define: {
"offset": 0,
"length": 32
},
in: {
template: "memory-range"
}
};

const cursor = await dereference(pointer, { templates });

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

expect(regions).toHaveLength(1);
expect(regions[0].offset).toEqual(Data.fromNumber(0));
expect(regions[0].length).toEqual(Data.fromNumber(32));
});
});
7 changes: 6 additions & 1 deletion packages/pointers/src/dereference/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export interface DereferenceOptions {
* Required for any pointers that reference the stack.
*/
state?: Machine.State;
templates?: Pointer.Templates
}

/**
Expand Down Expand Up @@ -43,11 +44,15 @@ export async function dereference(
* `generateRegions()` will potentially need.
*/
async function initializeGenerateRegionsOptions({
templates = {},
state: initialState
}: DereferenceOptions): Promise<Omit<GenerateRegionsOptions, "state">> {
const initialStackLength = initialState
? await initialState.stack.length
: 0n;

return { initialStackLength };
return {
templates,
initialStackLength
};
}
43 changes: 43 additions & 0 deletions packages/pointers/src/dereference/process.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { adjustStackLength, evaluateRegion } from "./region.js";
* Contextual information for use within a pointer dereference process
*/
export interface ProcessOptions {
templates: Pointer.Templates;
state: Machine.State;
stackLengthChange: bigint;
regions: Record<string, Cursor.Region>;
Expand Down Expand Up @@ -56,6 +57,10 @@ export async function* processPointer(
return yield* processScope(collection, options);
}

if (Pointer.Collection.isReference(collection)) {
return yield* processReference(collection, options);
}

console.error("%s", JSON.stringify(pointer, undefined, 2));
throw new Error("Unexpected unknown kind of pointer");
}
Expand Down Expand Up @@ -150,3 +155,41 @@ async function* processScope(
Memo.dereferencePointer(in_)
];
}

async function* processReference(
collection: Pointer.Collection.Reference,
options: ProcessOptions
): Process {
const { template: templateName } = collection;

const { templates, variables } = options;

const template = templates[templateName];

if (!template) {
throw new Error(
`Unknown pointer template named ${templateName}`
);
}

const {
expect: expectedVariables,
for: pointer
} = template;

const definedVariables = new Set(Object.keys(variables));
const missingVariables = expectedVariables
.filter(identifier => !definedVariables.has(identifier));

if (missingVariables.length > 0) {
throw new Error([
`Invalid reference to template named ${templateName}; missing expected `,
`variables with identifiers: ${missingVariables.join(", ")}. `,
`Please ensure these variables are defined prior to this reference.`
].join(""));
}

return [
Memo.dereferencePointer(pointer)
];
}
6 changes: 6 additions & 0 deletions packages/pointers/src/pointer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,12 @@ describe("type guards", () => {
},
guard: isPointer
},
{
schema: {
id: "schema:ethdebug/format/pointer/template"
},
guard: Pointer.isTemplate
},
] as const;

it.each(schemaGuards)("matches its examples", ({
Expand Down
43 changes: 40 additions & 3 deletions packages/pointers/src/pointer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,13 +129,16 @@ export namespace Pointer {
| Collection.Group
| Collection.List
| Collection.Conditional
| Collection.Scope;
| Collection.Scope
| Collection.Reference;

export const isCollection = (value: unknown): value is Collection =>
[
Collection.isGroup,
Collection.isList,
Collection.isConditional,
Collection.isScope
Collection.isScope,
Collection.isReference
].some(guard => guard(value));

export namespace Collection {
Expand Down Expand Up @@ -202,6 +205,16 @@ export namespace Pointer {
Object.keys(value.define).every(key => isIdentifier(key)) &&
"in" in value &&
isPointer(value.in);

export interface Reference {
template: string;
}

export const isReference = (value: unknown): value is Reference =>
!!value &&
typeof value === "object" &&
"template" in value &&
typeof value.template === "string" && !!value.template
}

export type Expression =
Expand Down Expand Up @@ -421,8 +434,32 @@ export namespace Pointer {
"$wordsized" in value &&
typeof value.$wordsized !== "undefined" &&
isExpression(value.$wordsized);
}
}

export interface Templates {
[identifier: string]: Pointer.Template;
}

}
export const isTemplates = (value: unknown): value is Templates =>
!!value &&
typeof value === "object" &&
Object.keys(value).every(isIdentifier) &&
Object.values(value).every(isTemplate);

export interface Template {
expect: string[];
for: Pointer;
}

export const isTemplate = (value: unknown): value is Template =>
!!value &&
typeof value === "object" &&
Object.keys(value).length === 2 &&
"expect" in value &&
value.expect instanceof Array &&
value.expect.every(isIdentifier) &&
"for" in value &&
isPointer(value.for);

}
8 changes: 7 additions & 1 deletion packages/pointers/test/observe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ export interface ObserveTraceOptions<V> {
*/
pointer: Pointer;

/**
* Pointer templates that may be referenced by the given pointer
*/
templates?: Pointer.Templates;

/**
* The necessary metadata and the Solidity source code for a contract whose
* `constructor()` manages the lifecycle of the variable that the specified
Expand Down Expand Up @@ -58,6 +63,7 @@ export interface ObserveTraceOptions<V> {
*/
export async function observeTrace<V>({
pointer,
templates = {},
compileOptions,
observe,
equals = (a, b) => a === b,
Expand Down Expand Up @@ -89,7 +95,7 @@ export async function observeTrace<V>({
}

if (!cursor) {
cursor = await dereference(pointer, { state });
cursor = await dereference(pointer, { state, templates });
}

const { regions, read } = await cursor.view(state);
Expand Down
18 changes: 18 additions & 0 deletions packages/web/spec/pointer/template.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
sidebar_position: 7
---

import SchemaViewer from "@site/src/components/SchemaViewer";

# Pointer templates

This format provides the concept of a **pointer template** to allow
deduplicating representations. Pointer templates are defined to specify the
variables they expect in scope and the pointer definition that uses those
variables.

<SchemaViewer
schema={{
id: "schema:ethdebug/format/pointer/template"
}}
/>
4 changes: 4 additions & 0 deletions packages/web/src/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,10 @@ export const schemaIndex: SchemaIndex = {
href: "/spec/pointer/expression"
},

"schema:ethdebug/format/pointer/template": {
href: "/spec/pointer/template"
},

...Object.entries({
Literal: {
title: "Literal values schema",
Expand Down
31 changes: 15 additions & 16 deletions schemas/pointer/collection.schema.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,34 +8,33 @@ type: object

allOf:
- oneOf:
- required:
- group
- required:
- list
- required:
- if
- required:
- define
- required: [group]
- required: [list]
- required: [if]
- required: [define]
- required: [template]

- if:
required:
- group
required: [group]
then:
$ref: "schema:ethdebug/format/pointer/collection/group"

- if:
required:
- list
required: [list]
then:
$ref: "schema:ethdebug/format/pointer/collection/list"

- if:
required:
- if
required: [if]
then:
$ref: "schema:ethdebug/format/pointer/collection/conditional"

- if:
required:
- define
required: [define]
then:
$ref: "schema:ethdebug/format/pointer/collection/scope"

- if:
required: [template]
then:
$ref: "schema:ethdebug/format/pointer/collection/reference"
21 changes: 21 additions & 0 deletions schemas/pointer/collection/reference.schema.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
$schema: "https://json-schema.org/draft/2020-12/schema"
$id: "schema:ethdebug/format/pointer/collection/reference"

title: ethdebug/format/pointer/collection/reference
description: |
A pointer by named reference to a pointer template (defined elsewhere).
type: object

properties:
template:
title: Template identifier
$ref: "schema:ethdebug/format/pointer/identifier"

required:
- template

additionalProperties: false

examples:
- template: "string-storage-pointer"
34 changes: 34 additions & 0 deletions schemas/pointer/template.schema.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
$schema: "https://json-schema.org/draft/2020-12/schema"
$id: "schema:ethdebug/format/pointer/template"

title: ethdebug/format/pointer/template
description: |
A schema for representing a pointer defined in terms of some variables whose
values are to be provided when invoking the template.
type: object
properties:
expect:
title: Template variables
description: |
An array of variable identifiers used in the definition of the
pointer template.
type: array
items:
$ref: "schema:ethdebug/format/pointer/identifier"
additionalItems: false

for:
$ref: "schema:ethdebug/format/pointer"

required:
- expect
- for

additionalProperties: false

examples:
- expect: ["slot"]
for:
location: storage
slot: "slot"

0 comments on commit fd9e14c

Please sign in to comment.