diff --git a/CHANGELOG.md b/CHANGELOG.md index 519bf11bde..be55167952 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -53,6 +53,21 @@ The sections do not signal obligations; the prefixes do. ## Unreleased +### Added + +- A region with `"location": "virtual"` holds the bytes of a computed value, + e.g., a variable the optimizer removed. It uses the new `fragment` + addressing scheme: a width-bearing `expression` supplies the bytes, and + optional `offset` and `length` select a range of them ([#411]). + - Schemas: **ethdebug/format/pointer/region/virtual**, + **ethdebug/format/pointer/scheme/fragment**, + **ethdebug/format/pointer/region** + - Producers: optional: a pointer may describe a value that no data location + stores as a virtual region. + - Consumers: required: a region may have `"location": "virtual"`. Its bytes + are the fragment of its `expression`'s value, which a consumer must + evaluate. A fragment outside the value's bytes is invalid. + ### Changed - The members of a `define` no longer depend on their order, since JSON @@ -763,3 +778,4 @@ First published version of the specification. [#402]: https://github.com/ethdebug/format/pull/402 [#404]: https://github.com/ethdebug/format/pull/404 [#408]: https://github.com/ethdebug/format/pull/408 +[#411]: https://github.com/ethdebug/format/pull/411 diff --git a/packages/format/CHANGELOG.md b/packages/format/CHANGELOG.md index 58918d31d2..0241128a54 100644 --- a/packages/format/CHANGELOG.md +++ b/packages/format/CHANGELOG.md @@ -6,6 +6,13 @@ tracked in the root [`CHANGELOG.md`](../../CHANGELOG.md). ## Unreleased +### Added + +- The **ethdebug/format/pointer/region/virtual** and + **ethdebug/format/pointer/scheme/fragment** schemas, with the types + `Pointer.Region.Virtual` and `Pointer.Scheme.Fragment` and their guards + `Pointer.Region.isVirtual` and `Pointer.Scheme.isFragment` ([#411]). + ### Changed - `schemas` parses each schema from YAML on first access instead of at @@ -156,6 +163,7 @@ First publication. [#323]: https://github.com/ethdebug/format/pull/323 [#325]: https://github.com/ethdebug/format/pull/325 [#373]: https://github.com/ethdebug/format/pull/373 +[#411]: https://github.com/ethdebug/format/pull/411 [`0ef2f37`]: https://github.com/ethdebug/format/commit/0ef2f37 [`10ab103`]: https://github.com/ethdebug/format/commit/10ab103 [`21e532e`]: https://github.com/ethdebug/format/commit/21e532e diff --git a/packages/format/src/types/pointer/pointer.test.ts b/packages/format/src/types/pointer/pointer.test.ts index 27211c9e9b..eb124e4895 100644 --- a/packages/format/src/types/pointer/pointer.test.ts +++ b/packages/format/src/types/pointer/pointer.test.ts @@ -160,6 +160,10 @@ testSchemaGuards("ethdebug/format/pointer", [ schema: "schema:ethdebug/format/pointer/region/code", guard: Pointer.Region.isCode, }, + { + schema: "schema:ethdebug/format/pointer/region/virtual", + guard: Pointer.Region.isVirtual, + }, { schema: "schema:ethdebug/format/pointer/collection/group", guard: Pointer.Collection.isGroup, diff --git a/packages/format/src/types/pointer/pointer.ts b/packages/format/src/types/pointer/pointer.ts index f5bc859c89..baf15b387d 100644 --- a/packages/format/src/types/pointer/pointer.ts +++ b/packages/format/src/types/pointer/pointer.ts @@ -15,7 +15,8 @@ export namespace Pointer { | Region.Calldata | Region.Returndata | Region.Transient - | Region.Code; + | Region.Code + | Region.Virtual; export const isRegion = (value: unknown): value is Region => [ @@ -26,6 +27,7 @@ export namespace Pointer { Region.isReturndata, Region.isTransient, Region.isCode, + Region.isVirtual, ].some((guard) => guard(value)); export namespace Region { @@ -71,6 +73,10 @@ export namespace Pointer { export type Code = Base & Scheme.Slice & { location: "code" }; export const isCode = (value: unknown): value is Code => isBase(value) && Scheme.isSlice(value) && value.location === "code"; + + export type Virtual = Base & Scheme.Fragment & { location: "virtual" }; + export const isVirtual = (value: unknown): value is Virtual => + isBase(value) && Scheme.isFragment(value) && value.location === "virtual"; } export namespace Scheme { @@ -99,6 +105,20 @@ export namespace Pointer { isExpression(value.offset) && "length" in value && isExpression(value.length); + + export interface Fragment { + expression: Expression; + offset?: Expression; + length?: Expression; + } + + export const isFragment = (value: unknown): value is Fragment => + !!value && + typeof value === "object" && + "expression" in value && + isExpression(value.expression) && + (!("offset" in value) || isExpression(value.offset)) && + (!("length" in value) || isExpression(value.length)); } export type Collection = diff --git a/packages/pointers/CHANGELOG.md b/packages/pointers/CHANGELOG.md index bf9623c77c..ef4a7e46b5 100644 --- a/packages/pointers/CHANGELOG.md +++ b/packages/pointers/CHANGELOG.md @@ -6,6 +6,13 @@ itself are tracked in the root [`CHANGELOG.md`](../../CHANGELOG.md). ## Unreleased +### Added + +- `dereference` and `read` support virtual regions. A concrete virtual + region keeps the bytes of its `expression` and has `offset` and `length` + filled in with their defaults. An expression with no width, or a fragment + outside the value, throws ([#411]). + ### Changed - `dereference` evaluates the members of a scope's `define` in dependency @@ -155,3 +162,4 @@ First publication. [#323]: https://github.com/ethdebug/format/pull/323 [#325]: https://github.com/ethdebug/format/pull/325 [#408]: https://github.com/ethdebug/format/pull/408 +[#411]: https://github.com/ethdebug/format/pull/411 diff --git a/packages/pointers/src/cursor.ts b/packages/pointers/src/cursor.ts index 56be57cd6c..53b6e925be 100644 --- a/packages/pointers/src/cursor.ts +++ b/packages/pointers/src/cursor.ts @@ -30,10 +30,11 @@ export namespace Cursor { /** * A Pointer region where all dynamic expressions have been replaced with - * concrete bytes values. + * concrete bytes values. A virtual region's `expression` becomes the + * bytes of its value. */ export type Region = { - [K in keyof R]: K extends "slot" | "offset" | "length" + [K in keyof R]: K extends "slot" | "offset" | "length" | "expression" ? R[K] extends Pointer.Expression ? Data : R[K] extends Pointer.Expression | undefined diff --git a/packages/pointers/src/dereference/index.test.ts b/packages/pointers/src/dereference/index.test.ts index b6feb1c83d..bb7b525585 100644 --- a/packages/pointers/src/dereference/index.test.ts +++ b/packages/pointers/src/dereference/index.test.ts @@ -893,6 +893,110 @@ describe("dereference", () => { expect(regions.lookup["my-slot-value"]).toBeDefined(); }); + describe("virtual regions", () => { + // `a` is on the stack, which reads as 0x11223344 + const a: Pointer.Region = { name: "a", location: "stack", slot: 2 }; + + const view = async (...group: Pointer[]) => + await (await dereference({ group: [a, ...group] })).view(state); + + const x: Pointer.Region = { + name: "x", + location: "virtual", + expression: { "~wordsized": { "~sum": [{ "~read": "a" }, 1] } }, + }; + + it("defaults to the whole value of the expression", async () => { + const { regions, read } = await view(x); + const value = Data.fromNumber(0x11223345).resizeTo(32); + + expect(regions.lookup["x"]).toEqual({ + name: "x", + location: "virtual", + expression: value, + offset: Data.fromUint(0n), + length: Data.fromNumber(32), + }); + expect(await read(regions.lookup["x"])).toEqual(value); + }); + + it("reads the fragment at an explicit offset and length", async () => { + const { regions, read } = await view( + { ...x, offset: 29, length: 2 }, + { + name: "rest", + location: "virtual", + expression: "0xdeadbeef", + offset: 1, + }, + ); + + expect(await read(regions.lookup["x"])).toEqual(Data.fromHex("0x2233")); + expect(await read(regions.lookup["rest"])).toEqual( + Data.fromHex("0xadbeef"), + ); + expect(regions.lookup["rest"].length).toEqual(Data.fromNumber(3)); + }); + + it("allows lookups of offset and length from a later region", async () => { + const { regions } = await view( + { + name: "tag", + location: "virtual", + expression: "0xdeadbeef", + offset: 1, + }, + { + location: "memory", + offset: { ".offset": "tag" }, + length: { ".length": "tag" }, + }, + ); + + expect(regions[2]).toEqual({ + location: "memory", + offset: Data.fromNumber(1), + length: Data.fromNumber(3), + }); + }); + + it("allows reading a virtual region in another expression", async () => { + const { regions, read } = await view(x, { + name: "y", + location: "virtual", + expression: { "~concat": ["0x00", { "~read": "x" }] }, + length: 2, + }); + + expect(await read(regions.lookup["y"])).toEqual(Data.fromHex("0x0000")); + expect(regions.lookup["y"]).toHaveProperty("expression.length", 33); + }); + + it("rejects a lookup of slot", async () => { + await expect( + view(x, { location: "storage", slot: { ".slot": "x" } }), + ).rejects.toThrow("Region named x does not have slot needed by lookup"); + }); + + it("rejects an expression with no width", async () => { + await expect( + view({ location: "virtual", expression: { "~sum": [1, 2] } }), + ).rejects.toThrow("evaluates to the integer 3, which has no byte width"); + }); + + it("rejects a fragment outside the value", async () => { + for (const fragment of [ + { offset: 5 }, + { offset: 2, length: 3 }, + { length: 5 }, + ]) { + await expect( + view({ location: "virtual", expression: "0xdeadbeef", ...fragment }), + ).rejects.toThrow("extends outside the 4 bytes"); + } + }); + }); + describe("names that start with $", () => { it("resolves a region named $this, not the current region", async () => { const pointer: Pointer = { diff --git a/packages/pointers/src/dereference/region.ts b/packages/pointers/src/dereference/region.ts index 8c855a37c2..a2d11c66d3 100644 --- a/packages/pointers/src/dereference/region.ts +++ b/packages/pointers/src/dereference/region.ts @@ -1,6 +1,6 @@ import { Pointer } from "@ethdebug/format"; import type { Cursor } from "#cursor"; -import type { Data } from "#data"; +import { Data } from "#data"; import { evaluate, Value, type EvaluateOptions } from "#evaluate"; /** @@ -86,12 +86,61 @@ export async function evaluateRegion( } } + if (Pointer.Region.isVirtual(region)) { + const fragment = await evaluateFragment( + region, + evaluatedProperties, + options, + ); + return fragment as Cursor.Region; + } + return { ...region, ...evaluatedProperties, } as Cursor.Region; } +/** + * Evaluate a virtual region's `expression` to the bytes it addresses into, + * and fill in the default `offset` and `length` so that later regions may + * look them up + */ +async function evaluateFragment( + region: Pointer.Region.Virtual, + { offset, length }: { offset?: Data; length?: Data }, + options: EvaluateOptions, +): Promise> { + const value = await evaluate(region.expression, options); + if (Value.isInteger(value)) { + throw new Error( + [ + `Expression of virtual region (${JSON.stringify(region.expression)}) `, + `evaluates to the integer ${value.value}, which has no byte `, + `width; give it a width with ~wordsized or ~sizedN`, + ].join(""), + ); + } + + const width = BigInt(value.data.length); + const start = offset?.asUint() ?? 0n; + const end = length ? start + length.asUint() : width; + if (start > width || end > width) { + const fields = length ? `${start}, length ${length.asUint()}` : start; + throw new Error( + `Fragment of virtual region (offset ${fields}) extends outside ` + + `the ${width} bytes of its expression's value`, + ); + } + + return { + ...region, + expression: value.data, + offset: Data.fromUint(start), + length: Data.fromUint(end - start), + }; +} + /** * Detect a stack region and modify its `slot` expression to include the * appropriate sum or difference based on the machine stack length change diff --git a/packages/pointers/src/read.test.ts b/packages/pointers/src/read.test.ts index 5824ed3911..467bfe5761 100644 --- a/packages/pointers/src/read.test.ts +++ b/packages/pointers/src/read.test.ts @@ -455,4 +455,23 @@ describe("read (segment carry)", () => { }); expect(result).toEqual(bytes([0xa2, 2], [0xa3, 2])); }); + + it("reads the fragment of a virtual region", async () => { + const expression = Data.fromHex("0xdeadbeef"); + + expect( + await read( + { + location: "virtual", + expression, + offset: Data.fromNumber(1), + length: Data.fromNumber(2), + }, + options, + ), + ).toEqual(Data.fromHex("0xadbe")); + expect(await read({ location: "virtual", expression }, options)).toEqual( + expression, + ); + }); }); diff --git a/packages/pointers/src/read.ts b/packages/pointers/src/read.ts index 05784be47b..e38bb028c5 100644 --- a/packages/pointers/src/read.ts +++ b/packages/pointers/src/read.ts @@ -84,6 +84,15 @@ export async function read( return await state.code.read({ slice: { offset, length } }); } + case "virtual": { + const { offset = 0n, length } = withPropertiesAsUints( + ["offset", "length"], + region, + ); + const end = length === undefined ? undefined : Number(offset + length); + + return Data.fromBytes(region.expression.subarray(Number(offset), end)); + } } } diff --git a/packages/pointers/test/test-cases.ts b/packages/pointers/test/test-cases.ts index 8e7d3942f6..32d439286a 100644 --- a/packages/pointers/test/test-cases.ts +++ b/packages/pointers/test/test-cases.ts @@ -237,6 +237,43 @@ const uint256ArrayMemoryTest: ObserveTraceTest = { }, }; +/** + * the computed values example: `x` (`a + 1`) and `tag` (the first 4 bytes + * of the hash of `a`) exist only as virtual regions computed from `a` + */ +const virtualRegionsTest: ObserveTraceTest<{ x: number; tag: string }> = { + pointer: findExamplePointer("virtual-operand"), + compileOptions: singleSourceCompilation({ + path: "VirtualRegions.sol", + contractName: "VirtualRegions", + content: `contract VirtualRegions { + uint256 a; + + constructor() { + a = 2; + } + } + `, + }), + + expectedValues: [ + { x: 1, tag: "0x290decd9" }, + { x: 3, tag: "0x405787fa" }, + ], + + async observe({ regions, read }) { + const x = Number((await read(regions.lookup["x"])).asUint()); + + const tag = (await read(regions.lookup["tag"])).toHex(); + + return { x, tag }; + }, + + equals(a, b) { + return a.x === b.x && a.tag === b.tag; + }, +}; + /** * collection of descriptions of tests that compile+deploy Solidity code, * then step through the machine trace of that code's execution, watching @@ -251,4 +288,5 @@ export const observeTraceTests = { "string storage": stringStorageTest, "string storage (multi-slot)": multiSlotStringStorageTest, "uint256[] memory": uint256ArrayMemoryTest, + "virtual regions": virtualRegionsTest, }; diff --git a/packages/web/docs/concepts/pointers.mdx b/packages/web/docs/concepts/pointers.mdx index 0bab12659c..c50a89ade5 100644 --- a/packages/web/docs/concepts/pointers.mdx +++ b/packages/web/docs/concepts/pointers.mdx @@ -186,6 +186,24 @@ optional offset and length for packed values: }`} +**Fragment-based regions** (virtual) hold the bytes of a computed value, +for data that no EVM location stores (e.g., a variable the optimizer removed). +Optional offset and length select part of the value: + + + {`{ + "location": "virtual", + "expression": { + "~keccak256": [{ "~wordsized": 1 }] + }, + "length": 4 +}`} + + Regions can be **named** for reference elsewhere in the pointer: sourceFile.getFunction("evaluateRegion")} /> + +A virtual region also has an `expression`. The implementation evaluates it +last, keeps its bytes as the concrete region's `expression`, and fills in the +default `offset` and `length`, so that later regions can look them up: + + sourceFile.getFunction("evaluateFragment")} +/> diff --git a/packages/web/docs/implementation-guides/pointers/testing/test-cases/test-cases.mdx b/packages/web/docs/implementation-guides/pointers/testing/test-cases/test-cases.mdx index 71729817dc..c2a2bd083e 100644 --- a/packages/web/docs/implementation-guides/pointers/testing/test-cases/test-cases.mdx +++ b/packages/web/docs/implementation-guides/pointers/testing/test-cases/test-cases.mdx @@ -23,6 +23,8 @@ Test cases are aggregated into the `observeTraceTests` variable: "/docs/implementation-guides/pointers/testing/test-cases/string-storage/", uint256ArrayMemoryTest: "/docs/implementation-guides/pointers/testing/test-cases/uint256-array-memory/", + virtualRegionsTest: + "/docs/implementation-guides/pointers/testing/test-cases/virtual-regions/", }} /> diff --git a/packages/web/docs/implementation-guides/pointers/testing/test-cases/virtual-regions.mdx b/packages/web/docs/implementation-guides/pointers/testing/test-cases/virtual-regions.mdx new file mode 100644 index 0000000000..7ab06b3378 --- /dev/null +++ b/packages/web/docs/implementation-guides/pointers/testing/test-cases/virtual-regions.mdx @@ -0,0 +1,27 @@ +--- +sidebar_position: 4 +sidebar_label: virtual regions +--- + +import CodeListing from "@site/src/components/CodeListing"; +import TestedPointer from "./TestedPointer"; + +# Test case: virtual regions + +A virtual region holds a computed value instead of stored bytes. This test +computes two values from a `uint256` in storage: `x`, the value plus one, +and `tag`, the first 4 bytes of the value's hash. + +## Test source + + + sourceFile.getVariableStatement("virtualRegionsTest") + } +/> + +## Tested pointer + + diff --git a/packages/web/spec/pointer/concepts.mdx b/packages/web/spec/pointer/concepts.mdx index a3cc05030e..ed312b7c6a 100644 --- a/packages/web/spec/pointer/concepts.mdx +++ b/packages/web/spec/pointer/concepts.mdx @@ -277,11 +277,15 @@ abstractions, but instead it defines the concept of an **addressing scheme**. Each location-specific region schema is defined as an extension of the schema for a particular addressing scheme. -This format currently defines two such addressing schemes: +This format currently defines three such addressing schemes: **ethdebug/format/pointer/scheme/slice** and **ethdebug/format/pointer/scheme/segment**, for addressing ranges within a single continuous byte array and addressing a slot or collection of slots -in a word-arranged locaton (respectively). +in a word-arranged locaton (respectively), and +**ethdebug/format/pointer/scheme/fragment**, which supplies its own byte +space (the value of an expression) and addresses a range within it. The +`virtual` location uses the fragment scheme for values that are computed +instead of stored. Within a slot, the segment scheme numbers bytes from the most significant byte: byte `0` is the first byte of the slot's big-endian word. diff --git a/packages/web/spec/pointer/region/location/virtual.mdx b/packages/web/spec/pointer/region/location/virtual.mdx new file mode 100644 index 0000000000..9026269c55 --- /dev/null +++ b/packages/web/spec/pointer/region/location/virtual.mdx @@ -0,0 +1,11 @@ +--- +sidebar_position: 8 +--- + +import SchemaViewer from "@site/src/components/SchemaViewer"; + +# `virtual` + + diff --git a/packages/web/spec/pointer/region/scheme/fragment.mdx b/packages/web/spec/pointer/region/scheme/fragment.mdx new file mode 100644 index 0000000000..b43c18462f --- /dev/null +++ b/packages/web/spec/pointer/region/scheme/fragment.mdx @@ -0,0 +1,11 @@ +--- +sidebar_position: 3 +--- + +import SchemaViewer from "@site/src/components/SchemaViewer"; + +# `fragment` + + diff --git a/packages/web/src/schemas.ts b/packages/web/src/schemas.ts index 2a82d08f0b..02cf5873e1 100644 --- a/packages/web/src/schemas.ts +++ b/packages/web/src/schemas.ts @@ -92,6 +92,7 @@ const pointerSchemaIndex: SchemaIndex = { "returndata", "transient", "code", + "virtual", ] .map((location) => ({ [`schema:ethdebug/format/pointer/region/${location}`]: { @@ -100,7 +101,7 @@ const pointerSchemaIndex: SchemaIndex = { })) .reduce((a, b) => ({ ...a, ...b }), {}), - ...["slice", "segment"] + ...["slice", "segment", "fragment"] .map((scheme) => ({ [`schema:ethdebug/format/pointer/scheme/${scheme}`]: { href: `/spec/pointer/region/scheme/${scheme}`, diff --git a/schemas/pointer.schema.yaml b/schemas/pointer.schema.yaml index ad9cf49727..404c5f1723 100644 --- a/schemas/pointer.schema.yaml +++ b/schemas/pointer.schema.yaml @@ -297,3 +297,26 @@ examples: slot: "start-slot" offset: 0 length: "string-length" + + - # example computed values: `x = a + 1` after the optimizer removed `x`, + # and `tag`, the first 4 bytes of the hash of `a`, where `a` is a + # `uint256` in storage slot 0 + group: + - name: "virtual-operand" + location: storage + slot: 0 + + - name: "x" + location: virtual + expression: + ~wordsized: + ~sum: + - ~read: "virtual-operand" + - 1 + + - name: "tag" + location: virtual + expression: + ~keccak256: + - ~read: "virtual-operand" + length: 4 diff --git a/schemas/pointer/region.schema.yaml b/schemas/pointer/region.schema.yaml index e2f7f5e705..364bf404e9 100644 --- a/schemas/pointer/region.schema.yaml +++ b/schemas/pointer/region.schema.yaml @@ -76,6 +76,15 @@ allOf: then: $ref: "schema:ethdebug/format/pointer/region/code" + - if: + required: + - location + properties: + location: + const: virtual + then: + $ref: "schema:ethdebug/format/pointer/region/virtual" + $defs: Location: type: string @@ -87,6 +96,7 @@ $defs: - returndata - transient - code + - virtual unevaluatedProperties: false diff --git a/schemas/pointer/region/virtual.schema.yaml b/schemas/pointer/region/virtual.schema.yaml new file mode 100644 index 0000000000..9802f6651f --- /dev/null +++ b/schemas/pointer/region/virtual.schema.yaml @@ -0,0 +1,46 @@ +$schema: "https://json-schema.org/draft/2020-12/schema" +$id: "schema:ethdebug/format/pointer/region/virtual" + +title: ethdebug/format/pointer/region/virtual +description: | + A schema for representing a region of data that is not stored in any EVM + data location, but is computed from the machine state: the bytes of an + expression's value. + + A compiler may use a virtual region for a value that has no place in the + machine state of its own, e.g., a variable the optimizer removed whose + value can still be computed from other values that remain. + + This schema is constructed by extending the base region schema + and the schema for the fragment addressing scheme. +type: object +allOf: + - title: '{ "location": "virtual" }' # note: whitespace chars are \255 (nbsp) + properties: + location: + const: virtual + + required: + - location + - $ref: "schema:ethdebug/format/pointer/region/base" + - $ref: "schema:ethdebug/format/pointer/scheme/fragment" + +unevaluatedProperties: false + +examples: + # `x = a + 1` after the optimizer removed `x`, where a region named `a` + # comes before this one + - name: x + location: virtual + expression: + ~wordsized: + ~sum: + - ~read: a + - 1 + # the first 4 bytes of a hash + - name: tag + location: virtual + expression: + ~keccak256: + - ~read: a + length: 4 diff --git a/schemas/pointer/scheme/fragment.schema.yaml b/schemas/pointer/scheme/fragment.schema.yaml new file mode 100644 index 0000000000..3e4dcf7c46 --- /dev/null +++ b/schemas/pointer/scheme/fragment.schema.yaml @@ -0,0 +1,66 @@ +$schema: "https://json-schema.org/draft/2020-12/schema" +$id: "schema:ethdebug/format/pointer/scheme/fragment" + +title: ethdebug/format/pointer/scheme/fragment +description: | + An addressing scheme for pointing to a range of bytes inside the value of + an expression. + + The other addressing schemes address bytes in a byte space that the data + location already has. This scheme supplies the byte space itself: the + bytes of the value that `expression` evaluates to. It then addresses a + range of bytes in that value by `offset` and `length`, as the slice + scheme does. + + A fragment that extends outside the value's bytes (i.e., where `offset` + plus `length` exceeds the value's width) is invalid. + +type: object + +properties: + expression: + description: | + The expression whose value supplies the bytes for this fragment. + + This expression **must** be width-bearing (bytes), the same rule as + for the operands of `~concat` and `~keccak256`. A bare integer (a JSON + number, an odd-digit hex literal, `~wordsize`, an arithmetic result, + or a lookup) is **not** valid here: give it a width first with + `~sizedN` or `~wordsized`. + + This expression is evaluated against the same machine state as the + other regions of the pointer. It may refer only to regions that come + before this region. + $ref: "schema:ethdebug/format/pointer/expression" + offset: + description: | + The index of the byte (starting from zero) in the value's bytes where + the fragment begins. + + This field is **optional**. If unspecified, it has the default value + of `0`, indicating that the fragment begins at the first byte of the + value. + $ref: "schema:ethdebug/format/pointer/expression" + default: 0 + length: + description: | + The length of the fragment in number of bytes. + + This field is **optional**. If unspecified, its default value + indicates that the fragment ends at the end of the value: the value's + width minus `offset`. + $ref: "schema:ethdebug/format/pointer/expression" + +required: + - expression + +examples: + - expression: + ~wordsized: 1 + - expression: + ~keccak256: + - ~wordsized: 0 + length: 4 + - expression: "0xdeadbeef" + offset: 2 + length: 2