From 3b959c5de9562bc2b2ea664c20651a7f2bde7a11 Mon Sep 17 00:00:00 2001 From: "g. nicholas d'andrea" Date: Wed, 28 Aug 2024 17:08:08 +0200 Subject: [PATCH 1/4] Define pointer templates --- packages/pointers/src/dereference/generate.ts | 3 + .../pointers/src/dereference/index.test.ts | 31 +++++ packages/pointers/src/dereference/index.ts | 7 +- packages/pointers/src/dereference/process.ts | 43 ++++++ packages/pointers/src/pointer.test.ts | 6 + packages/pointers/src/pointer.ts | 43 +++++- packages/pointers/src/test-cases.ts | 4 +- packages/pointers/test/examples.ts | 12 ++ packages/pointers/test/index.ts | 2 +- packages/pointers/test/observe.ts | 8 +- schemas/pointer.schema.yaml | 94 +------------ schemas/pointer/collection.schema.yaml | 31 ++--- .../pointer/collection/reference.schema.yaml | 21 +++ schemas/pointer/templates.schema.yaml | 131 ++++++++++++++++++ 14 files changed, 322 insertions(+), 114 deletions(-) create mode 100644 schemas/pointer/collection/reference.schema.yaml create mode 100644 schemas/pointer/templates.schema.yaml diff --git a/packages/pointers/src/dereference/generate.ts b/packages/pointers/src/dereference/generate.ts index 6b39bec5..9cc97ff2 100644 --- a/packages/pointers/src/dereference/generate.ts +++ b/packages/pointers/src/dereference/generate.ts @@ -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; } @@ -62,6 +63,7 @@ export async function* generateRegions( } async function initializeProcessOptions({ + templates, state, initialStackLength }: GenerateRegionsOptions): Promise { @@ -72,6 +74,7 @@ async function initializeProcessOptions({ const variables: Record = {}; return { + templates, state, stackLengthChange, regions, diff --git a/packages/pointers/src/dereference/index.test.ts b/packages/pointers/src/dereference/index.test.ts index b5983e47..894c40ae 100644 --- a/packages/pointers/src/dereference/index.test.ts +++ b/packages/pointers/src/dereference/index.test.ts @@ -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)); + }); }); diff --git a/packages/pointers/src/dereference/index.ts b/packages/pointers/src/dereference/index.ts index 949dc3b8..15172245 100644 --- a/packages/pointers/src/dereference/index.ts +++ b/packages/pointers/src/dereference/index.ts @@ -11,6 +11,7 @@ export interface DereferenceOptions { * Required for any pointers that reference the stack. */ state?: Machine.State; + templates?: Pointer.Templates } /** @@ -43,11 +44,15 @@ export async function dereference( * `generateRegions()` will potentially need. */ async function initializeGenerateRegionsOptions({ + templates = {}, state: initialState }: DereferenceOptions): Promise> { const initialStackLength = initialState ? await initialState.stack.length : 0n; - return { initialStackLength }; + return { + templates, + initialStackLength + }; } diff --git a/packages/pointers/src/dereference/process.ts b/packages/pointers/src/dereference/process.ts index 24e7465c..61a866cb 100644 --- a/packages/pointers/src/dereference/process.ts +++ b/packages/pointers/src/dereference/process.ts @@ -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; @@ -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"); } @@ -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) + ]; +} diff --git a/packages/pointers/src/pointer.test.ts b/packages/pointers/src/pointer.test.ts index 9b18421a..42f9b873 100644 --- a/packages/pointers/src/pointer.test.ts +++ b/packages/pointers/src/pointer.test.ts @@ -176,6 +176,12 @@ describe("type guards", () => { }, guard: isPointer }, + { + schema: { + id: "schema:ethdebug/format/pointer/templates" + }, + guard: Pointer.isTemplates + }, ] as const; it.each(schemaGuards)("matches its examples", ({ diff --git a/packages/pointers/src/pointer.ts b/packages/pointers/src/pointer.ts index 8d37bd53..594f8319 100644 --- a/packages/pointers/src/pointer.ts +++ b/packages/pointers/src/pointer.ts @@ -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 { @@ -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 = @@ -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); + } diff --git a/packages/pointers/src/test-cases.ts b/packages/pointers/src/test-cases.ts index 91830a5b..0e60ed85 100644 --- a/packages/pointers/src/test-cases.ts +++ b/packages/pointers/src/test-cases.ts @@ -1,6 +1,7 @@ import { singleSourceCompilation, findExamplePointer, + findExampleTemplates, type ObserveTraceOptions } from "../test/index.js"; import { type Cursor, Data } from "./index.js"; @@ -80,7 +81,8 @@ const structStorageTest: ObserveTraceTest<{ }; const stringStorageTest: ObserveTraceTest = { - pointer: findExamplePointer("string-storage-contract-variable-slot"), + pointer: findExamplePointer("solidity-string-storage"), + templates: findExampleTemplates(), compileOptions: singleSourceCompilation({ path: "StringStorage.sol", diff --git a/packages/pointers/test/examples.ts b/packages/pointers/test/examples.ts index 1c4d7663..1e7eed81 100644 --- a/packages/pointers/test/examples.ts +++ b/packages/pointers/test/examples.ts @@ -16,3 +16,15 @@ export const findExamplePointer = (() => { examplePointers .find(pointer => JSON.stringify(pointer).includes(text))!; })(); + +export const findExampleTemplates = (() => { + const { + schema: { + examples: [exampleTemplates] + } + } = describeSchema({ + schema: { id: "schema:ethdebug/format/pointer/templates" } + }) as { schema: { examples: Pointer.Templates[] } }; + + return (): Pointer.Templates => exampleTemplates; +})(); diff --git a/packages/pointers/test/index.ts b/packages/pointers/test/index.ts index 96114464..6ac60131 100644 --- a/packages/pointers/test/index.ts +++ b/packages/pointers/test/index.ts @@ -11,7 +11,7 @@ export { export { deployContract, } from "./deploy.js"; -export { findExamplePointer } from "./examples.js"; +export { findExamplePointer, findExampleTemplates } from "./examples.js"; export { observeTrace, diff --git a/packages/pointers/test/observe.ts b/packages/pointers/test/observe.ts index f9e36039..2b6b5277 100644 --- a/packages/pointers/test/observe.ts +++ b/packages/pointers/test/observe.ts @@ -11,6 +11,11 @@ export interface ObserveTraceOptions { */ 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 @@ -58,6 +63,7 @@ export interface ObserveTraceOptions { */ export async function observeTrace({ pointer, + templates = {}, compileOptions, observe, equals = (a, b) => a === b, @@ -89,7 +95,7 @@ export async function observeTrace({ } if (!cursor) { - cursor = await dereference(pointer, { state }); + cursor = await dereference(pointer, { state, templates }); } const { regions, read } = await cursor.view(state); diff --git a/schemas/pointer.schema.yaml b/schemas/pointer.schema.yaml index 82d367d4..d8adf1d1 100644 --- a/schemas/pointer.schema.yaml +++ b/schemas/pointer.schema.yaml @@ -151,96 +151,8 @@ examples: - .length: "struct-member-0" length: $wordsize - - # example `string storage` allocation + - # example template reference define: - "string-storage-contract-variable-slot": 0 + "contract-variable-slot": 0 in: - group: - # for short strings, the length is stored as 2n in the last byte of slot - - name: "length-flag" - location: storage - slot: "string-storage-contract-variable-slot" - offset: - $difference: [$wordsize, 1] - length: 1 - - # define the region representing the string data itself conditionally - # based on odd or even length data - - if: - $remainder: - - $sum: - - $read: "length-flag" - - 1 - - 2 - - # short string case (flag is even) - then: - define: - "string-length": - $quotient: [{ $read: "length-flag" }, 2] - in: - name: "string" - location: storage - slot: "string-storage-contract-variable-slot" - offset: 0 - length: "string-length" - - # long string case (flag is odd) - else: - group: - # long strings may use full word to describe length as 2n+1 - - name: "long-string-length-data" - location: storage - slot: "string-storage-contract-variable-slot" - offset: 0 - length: $wordsize - - - define: - "string-length": - $quotient: - - $difference: - - $read: "long-string-length-data" - - 1 - - 2 - - "start-slot": - $keccak256: - - $wordsized: "string-storage-contract-variable-slot" - - "total-slots": - # account for both zero and nonzero slot remainders by adding - # $wordsize-1 to the length before dividing - $quotient: - - $sum: ["string-length", { $difference: [$wordsize, 1] }] - - $wordsize - in: - list: - count: "total-slots" - each: "i" - is: - define: - "current-slot": - $sum: ["start-slot", "i"] - "previous-length": - $product: ["i", $wordsize] - in: - # conditional based on whether this is the last slot: - # is the string length longer than the previous length - # plus this whole slot? - if: - $difference: - - "string-length" - - $sum: ["previous-length", "$wordsize"] - then: - # include the whole slot - name: "string" - location: storage - slot: "current-slot" - else: - # include only what's left in the string - name: "string" - location: storage - slot: "current-slot" - offset: 0 - length: - $difference: ["string-length", "previous-length"] + template: "solidity-string-storage" diff --git a/schemas/pointer/collection.schema.yaml b/schemas/pointer/collection.schema.yaml index 556e830c..145dc9a2 100644 --- a/schemas/pointer/collection.schema.yaml +++ b/schemas/pointer/collection.schema.yaml @@ -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" diff --git a/schemas/pointer/collection/reference.schema.yaml b/schemas/pointer/collection/reference.schema.yaml new file mode 100644 index 00000000..6889231c --- /dev/null +++ b/schemas/pointer/collection/reference.schema.yaml @@ -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" diff --git a/schemas/pointer/templates.schema.yaml b/schemas/pointer/templates.schema.yaml new file mode 100644 index 00000000..1a62a29c --- /dev/null +++ b/schemas/pointer/templates.schema.yaml @@ -0,0 +1,131 @@ +$schema: "https://json-schema.org/draft/2020-12/schema" +$id: "schema:ethdebug/format/pointer/templates" + +title: ethdebug/format/pointer/templates +description: | + A schema for representing a named set of pointer templates. + +type: object + +patternProperties: + "^[a-zA-Z_\\-]+[a-zA-Z0-9$_\\-]*$": + $ref: "#/$defs/Template" + +additionalProperties: false + +$defs: + Template: + title: Pointer template + type: object + properties: + expect: + type: array + items: + $ref: "schema:ethdebug/format/pointer/identifier" + additionalItems: false + + for: + $ref: "schema:ethdebug/format/pointer" + + required: + - expect + - for + + additionalProperties: false + +examples: + - "solidity-string-storage": + expect: + - "contract-variable-slot" + for: + group: + # for short strings, the length is stored as 2n in the last byte of slot + - name: "length-flag" + location: storage + slot: "contract-variable-slot" + offset: + $difference: [$wordsize, 1] + length: 1 + + # define the region representing the string data itself conditionally + # based on odd or even length data + - if: + $remainder: + - $sum: + - $read: "length-flag" + - 1 + - 2 + + # short string case (flag is even) + then: + define: + "string-length": + $quotient: [{ $read: "length-flag" }, 2] + in: + name: "string" + location: storage + slot: "contract-variable-slot" + offset: 0 + length: "string-length" + + # long string case (flag is odd) + else: + group: + # long strings may use full word to describe length as 2n+1 + - name: "long-string-length-data" + location: storage + slot: "contract-variable-slot" + offset: 0 + length: $wordsize + + - define: + "string-length": + $quotient: + - $difference: + - $read: "long-string-length-data" + - 1 + - 2 + + "start-slot": + $keccak256: + - $wordsized: "contract-variable-slot" + + "total-slots": + # account for both zero and nonzero slot remainders by adding + # $wordsize-1 to the length before dividing + $quotient: + - $sum: ["string-length", { $difference: [$wordsize, 1] }] + - $wordsize + in: + list: + count: "total-slots" + each: "i" + is: + define: + "current-slot": + $sum: ["start-slot", "i"] + "previous-length": + $product: ["i", $wordsize] + in: + # conditional based on whether this is the last slot: + # is the string length longer than the previous length + # plus this whole slot? + if: + $difference: + - "string-length" + - $sum: ["previous-length", "$wordsize"] + then: + # include the whole slot + name: "string" + location: storage + slot: "current-slot" + else: + # include only what's left in the string + name: "string" + location: storage + slot: "current-slot" + offset: 0 + length: + $difference: ["string-length", "previous-length"] + + From d5fcc2a1bd691bd4778717eaff6b2789e4fd6c26 Mon Sep 17 00:00:00 2001 From: "g. nicholas d'andrea" Date: Wed, 28 Aug 2024 18:07:08 +0200 Subject: [PATCH 2/4] Fix pointer reference --- .../pointers/testing/test-cases/string-storage.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/web/docs/implementation-guides/pointers/testing/test-cases/string-storage.mdx b/packages/web/docs/implementation-guides/pointers/testing/test-cases/string-storage.mdx index bc52f59a..9f0f959a 100644 --- a/packages/web/docs/implementation-guides/pointers/testing/test-cases/string-storage.mdx +++ b/packages/web/docs/implementation-guides/pointers/testing/test-cases/string-storage.mdx @@ -25,5 +25,5 @@ value. ## Tested pointer From c36e01804624ec0a29bc89448fcd71f7a175906a Mon Sep 17 00:00:00 2001 From: "g. nicholas d'andrea" Date: Thu, 29 Aug 2024 14:03:02 +0200 Subject: [PATCH 3/4] Add pointer templates to specification pages --- packages/web/spec/pointer/templates.mdx | 33 +++++++++++++++++++++++++ packages/web/src/schemas.ts | 9 +++++++ schemas/pointer/templates.schema.yaml | 12 ++++++++- 3 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 packages/web/spec/pointer/templates.mdx diff --git a/packages/web/spec/pointer/templates.mdx b/packages/web/spec/pointer/templates.mdx new file mode 100644 index 00000000..26d4931c --- /dev/null +++ b/packages/web/spec/pointer/templates.mdx @@ -0,0 +1,33 @@ +--- +sidebar_position: 7 +--- + +import SchemaViewer from "@site/src/components/SchemaViewer"; + +# Pointer templates + +This format provides the concept of **pointer templates** to allow +deduplicating representations. Pointer templates are defined to specify the +variables they expect in scope and the pointer definition that uses those +variables. + +## Template schema {#template-schema} + +The following schema defines how to represent a single pointer template. +See [below](#templates-schema) for how to organize pointer templates by name. + + + +## Organizing templates {#templates-schema} + +The **ethdebug/format/pointer/templates** schema specifies how to represent +a collection of pointer templates by name. + + diff --git a/packages/web/src/schemas.ts b/packages/web/src/schemas.ts index a7b8f60a..f2b4ec47 100644 --- a/packages/web/src/schemas.ts +++ b/packages/web/src/schemas.ts @@ -116,6 +116,15 @@ export const schemaIndex: SchemaIndex = { href: "/spec/pointer/expression" }, + "schema:ethdebug/format/pointer/templates": { + href: "/spec/pointer/templates" + }, + + "schema:ethdebug/format/pointer/templates#/$defs/Template": { + title: "Pointer template schema", + href: "/spec/pointer/templates#template-schema" + }, + ...Object.entries({ Literal: { title: "Literal values schema", diff --git a/schemas/pointer/templates.schema.yaml b/schemas/pointer/templates.schema.yaml index 1a62a29c..e6bca853 100644 --- a/schemas/pointer/templates.schema.yaml +++ b/schemas/pointer/templates.schema.yaml @@ -15,10 +15,14 @@ additionalProperties: false $defs: Template: - title: Pointer template + title: Pointer template schema 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" @@ -33,6 +37,12 @@ $defs: additionalProperties: false + examples: + - expect: ["slot"] + for: + location: storage + slot: "slot" + examples: - "solidity-string-storage": expect: From 8e983c7d8d20c2ef66bfef7cae9f49a5e9245175 Mon Sep 17 00:00:00 2001 From: "g. nicholas d'andrea" Date: Fri, 30 Aug 2024 13:14:40 +0200 Subject: [PATCH 4/4] Remove ethdebug/format/pointer/templates ... and just define ethdebug/format/pointer/template instead --- packages/pointers/src/pointer.test.ts | 4 +- packages/pointers/src/test-cases.ts | 4 +- packages/pointers/test/examples.ts | 12 -- packages/pointers/test/index.ts | 2 +- .../testing/test-cases/string-storage.mdx | 2 +- packages/web/spec/pointer/templates.mdx | 19 +-- packages/web/src/schemas.ts | 9 +- schemas/pointer.schema.yaml | 92 +++++++++++- schemas/pointer/template.schema.yaml | 34 +++++ schemas/pointer/templates.schema.yaml | 141 ------------------ 10 files changed, 133 insertions(+), 186 deletions(-) create mode 100644 schemas/pointer/template.schema.yaml delete mode 100644 schemas/pointer/templates.schema.yaml diff --git a/packages/pointers/src/pointer.test.ts b/packages/pointers/src/pointer.test.ts index 42f9b873..0b12c7e3 100644 --- a/packages/pointers/src/pointer.test.ts +++ b/packages/pointers/src/pointer.test.ts @@ -178,9 +178,9 @@ describe("type guards", () => { }, { schema: { - id: "schema:ethdebug/format/pointer/templates" + id: "schema:ethdebug/format/pointer/template" }, - guard: Pointer.isTemplates + guard: Pointer.isTemplate }, ] as const; diff --git a/packages/pointers/src/test-cases.ts b/packages/pointers/src/test-cases.ts index 0e60ed85..91830a5b 100644 --- a/packages/pointers/src/test-cases.ts +++ b/packages/pointers/src/test-cases.ts @@ -1,7 +1,6 @@ import { singleSourceCompilation, findExamplePointer, - findExampleTemplates, type ObserveTraceOptions } from "../test/index.js"; import { type Cursor, Data } from "./index.js"; @@ -81,8 +80,7 @@ const structStorageTest: ObserveTraceTest<{ }; const stringStorageTest: ObserveTraceTest = { - pointer: findExamplePointer("solidity-string-storage"), - templates: findExampleTemplates(), + pointer: findExamplePointer("string-storage-contract-variable-slot"), compileOptions: singleSourceCompilation({ path: "StringStorage.sol", diff --git a/packages/pointers/test/examples.ts b/packages/pointers/test/examples.ts index 1e7eed81..1c4d7663 100644 --- a/packages/pointers/test/examples.ts +++ b/packages/pointers/test/examples.ts @@ -16,15 +16,3 @@ export const findExamplePointer = (() => { examplePointers .find(pointer => JSON.stringify(pointer).includes(text))!; })(); - -export const findExampleTemplates = (() => { - const { - schema: { - examples: [exampleTemplates] - } - } = describeSchema({ - schema: { id: "schema:ethdebug/format/pointer/templates" } - }) as { schema: { examples: Pointer.Templates[] } }; - - return (): Pointer.Templates => exampleTemplates; -})(); diff --git a/packages/pointers/test/index.ts b/packages/pointers/test/index.ts index 6ac60131..96114464 100644 --- a/packages/pointers/test/index.ts +++ b/packages/pointers/test/index.ts @@ -11,7 +11,7 @@ export { export { deployContract, } from "./deploy.js"; -export { findExamplePointer, findExampleTemplates } from "./examples.js"; +export { findExamplePointer } from "./examples.js"; export { observeTrace, diff --git a/packages/web/docs/implementation-guides/pointers/testing/test-cases/string-storage.mdx b/packages/web/docs/implementation-guides/pointers/testing/test-cases/string-storage.mdx index 9f0f959a..bc52f59a 100644 --- a/packages/web/docs/implementation-guides/pointers/testing/test-cases/string-storage.mdx +++ b/packages/web/docs/implementation-guides/pointers/testing/test-cases/string-storage.mdx @@ -25,5 +25,5 @@ value. ## Tested pointer diff --git a/packages/web/spec/pointer/templates.mdx b/packages/web/spec/pointer/templates.mdx index 26d4931c..6f7109e9 100644 --- a/packages/web/spec/pointer/templates.mdx +++ b/packages/web/spec/pointer/templates.mdx @@ -6,28 +6,13 @@ import SchemaViewer from "@site/src/components/SchemaViewer"; # Pointer templates -This format provides the concept of **pointer templates** to allow +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. -## Template schema {#template-schema} - -The following schema defines how to represent a single pointer template. -See [below](#templates-schema) for how to organize pointer templates by name. - - -## Organizing templates {#templates-schema} - -The **ethdebug/format/pointer/templates** schema specifies how to represent -a collection of pointer templates by name. - - diff --git a/packages/web/src/schemas.ts b/packages/web/src/schemas.ts index f2b4ec47..65a2a87b 100644 --- a/packages/web/src/schemas.ts +++ b/packages/web/src/schemas.ts @@ -116,13 +116,8 @@ export const schemaIndex: SchemaIndex = { href: "/spec/pointer/expression" }, - "schema:ethdebug/format/pointer/templates": { - href: "/spec/pointer/templates" - }, - - "schema:ethdebug/format/pointer/templates#/$defs/Template": { - title: "Pointer template schema", - href: "/spec/pointer/templates#template-schema" + "schema:ethdebug/format/pointer/template": { + href: "/spec/pointer/template" }, ...Object.entries({ diff --git a/schemas/pointer.schema.yaml b/schemas/pointer.schema.yaml index d8adf1d1..9346ff36 100644 --- a/schemas/pointer.schema.yaml +++ b/schemas/pointer.schema.yaml @@ -153,6 +153,94 @@ examples: - # example template reference define: - "contract-variable-slot": 0 + "string-storage-contract-variable-slot": 0 in: - template: "solidity-string-storage" + group: + # for short strings, the length is stored as 2n in the last byte of slot + - name: "length-flag" + location: storage + slot: "string-storage-contract-variable-slot" + offset: + $difference: [$wordsize, 1] + length: 1 + + # define the region representing the string data itself conditionally + # based on odd or even length data + - if: + $remainder: + - $sum: + - $read: "length-flag" + - 1 + - 2 + + # short string case (flag is even) + then: + define: + "string-length": + $quotient: [{ $read: "length-flag" }, 2] + in: + name: "string" + location: storage + slot: "string-storage-contract-variable-slot" + offset: 0 + length: "string-length" + + # long string case (flag is odd) + else: + group: + # long strings may use full word to describe length as 2n+1 + - name: "long-string-length-data" + location: storage + slot: "string-storage-contract-variable-slot" + offset: 0 + length: $wordsize + + - define: + "string-length": + $quotient: + - $difference: + - $read: "long-string-length-data" + - 1 + - 2 + + "start-slot": + $keccak256: + - $wordsized: "string-storage-contract-variable-slot" + + "total-slots": + # account for both zero and nonzero slot remainders by adding + # $wordsize-1 to the length before dividing + $quotient: + - $sum: ["string-length", { $difference: [$wordsize, 1] }] + - $wordsize + in: + list: + count: "total-slots" + each: "i" + is: + define: + "current-slot": + $sum: ["start-slot", "i"] + "previous-length": + $product: ["i", $wordsize] + in: + # conditional based on whether this is the last slot: + # is the string length longer than the previous length + # plus this whole slot? + if: + $difference: + - "string-length" + - $sum: ["previous-length", "$wordsize"] + then: + # include the whole slot + name: "string" + location: storage + slot: "current-slot" + else: + # include only what's left in the string + name: "string" + location: storage + slot: "current-slot" + offset: 0 + length: + $difference: ["string-length", "previous-length"] diff --git a/schemas/pointer/template.schema.yaml b/schemas/pointer/template.schema.yaml new file mode 100644 index 00000000..5324685a --- /dev/null +++ b/schemas/pointer/template.schema.yaml @@ -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" diff --git a/schemas/pointer/templates.schema.yaml b/schemas/pointer/templates.schema.yaml deleted file mode 100644 index e6bca853..00000000 --- a/schemas/pointer/templates.schema.yaml +++ /dev/null @@ -1,141 +0,0 @@ -$schema: "https://json-schema.org/draft/2020-12/schema" -$id: "schema:ethdebug/format/pointer/templates" - -title: ethdebug/format/pointer/templates -description: | - A schema for representing a named set of pointer templates. - -type: object - -patternProperties: - "^[a-zA-Z_\\-]+[a-zA-Z0-9$_\\-]*$": - $ref: "#/$defs/Template" - -additionalProperties: false - -$defs: - Template: - title: Pointer template schema - 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" - -examples: - - "solidity-string-storage": - expect: - - "contract-variable-slot" - for: - group: - # for short strings, the length is stored as 2n in the last byte of slot - - name: "length-flag" - location: storage - slot: "contract-variable-slot" - offset: - $difference: [$wordsize, 1] - length: 1 - - # define the region representing the string data itself conditionally - # based on odd or even length data - - if: - $remainder: - - $sum: - - $read: "length-flag" - - 1 - - 2 - - # short string case (flag is even) - then: - define: - "string-length": - $quotient: [{ $read: "length-flag" }, 2] - in: - name: "string" - location: storage - slot: "contract-variable-slot" - offset: 0 - length: "string-length" - - # long string case (flag is odd) - else: - group: - # long strings may use full word to describe length as 2n+1 - - name: "long-string-length-data" - location: storage - slot: "contract-variable-slot" - offset: 0 - length: $wordsize - - - define: - "string-length": - $quotient: - - $difference: - - $read: "long-string-length-data" - - 1 - - 2 - - "start-slot": - $keccak256: - - $wordsized: "contract-variable-slot" - - "total-slots": - # account for both zero and nonzero slot remainders by adding - # $wordsize-1 to the length before dividing - $quotient: - - $sum: ["string-length", { $difference: [$wordsize, 1] }] - - $wordsize - in: - list: - count: "total-slots" - each: "i" - is: - define: - "current-slot": - $sum: ["start-slot", "i"] - "previous-length": - $product: ["i", $wordsize] - in: - # conditional based on whether this is the last slot: - # is the string length longer than the previous length - # plus this whole slot? - if: - $difference: - - "string-length" - - $sum: ["previous-length", "$wordsize"] - then: - # include the whole slot - name: "string" - location: storage - slot: "current-slot" - else: - # include only what's left in the string - name: "string" - location: storage - slot: "current-slot" - offset: 0 - length: - $difference: ["string-length", "previous-length"] - -