Reading from pointer regions
Being able to read a particular pointer region's data from a machine state
becomes straightforward by restricting the read() logic to accept only
concrete
Cursor.Region
objects (rather than attempting to evaluate pointer expressions at the same
time).
Besides just a switch() statement for all the different locations of regions,
this implementation uses a small helper function to convert relevant property
values from raw bytes into unsigned integer bigints.
Code listing
src/read.ts
import { Pointer } from "@ethdebug/format";
import { Machine } from "./machine.js";
import { Data } from "./data.js";
import type { Cursor } from "./cursor.js";
export interface ReadOptions {
state: Machine.State;
}
export async function read(
region: Cursor.Region,
options: ReadOptions,
): Promise<Data> {
const { location } = region;
const { state } = options;
switch (location) {
case "stack": {
const {
slot,
offset = 0n,
length = 32n,
} = withPropertiesAsUints(["slot", "offset", "length"], region);
return await state.stack.peek({
depth: slot,
slice: {
offset,
length,
},
});
}
case "memory": {
const { offset, length } = withPropertiesAsUints(
["offset", "length"],
region,
);
return await state.memory.read({
slice: {
offset: offset,
length: length,
},
});
}
case "storage": {
const { slot } = region;
const { offset = 0n, length = 32n } = withPropertiesAsUints(
["offset", "length"],
region,
);
return await state.storage.read({
slot,
slice: {
offset,
length,
},
});
}
case "calldata": {
const { offset, length } = withPropertiesAsUints(
["offset", "length"],
region,
);
return await state.calldata.read({ slice: { offset, length } });
}
case "returndata": {
const { offset, length } = withPropertiesAsUints(
["offset", "length"],
region,
);
return await state.returndata.read({ slice: { offset, length } });
}
case "transient": {
const { slot } = region;
const { offset = 0n, length = 32n } = withPropertiesAsUints(
["offset", "length"],
region,
);
return await state.transient.read({
slot,
slice: {
offset,
length,
},
});
}
case "code": {
const { offset, length } = withPropertiesAsUints(
["offset", "length"],
region,
);
return await state.code.read({ slice: { offset, length } });
}
}
}
type DataProperties<R extends Pointer.Region> = {
[K in keyof Cursor.Region<R> &
("slot" | "offset" | "length")]: Cursor.Region<R>[K];
};
type PickDataPropertiesAsUints<
R extends Pointer.Region,
U extends keyof DataProperties<R>,
> = {
[K in U]: undefined extends Cursor.Region<R>[K] ? bigint | undefined : bigint;
};
function withPropertiesAsUints<
R extends Pointer.Region,
U extends keyof DataProperties<R>,
>(uintKeys: U[], region: Cursor.Region<R>): PickDataPropertiesAsUints<R, U> {
const result: Partial<PickDataPropertiesAsUints<R, U>> = {};
for (const key of uintKeys) {
const data: Data | undefined = region[key] as Data | undefined;
if (typeof data !== "undefined") {
result[key] = data.asUint();
}
}
return result as PickDataPropertiesAsUints<R, U>;
}