Skip to content

Commit

Permalink
Implement pointer dereferencing in TypeScript
Browse files Browse the repository at this point in the history
- Define new package @ethdebug/pointers
  - Setup TS build system and Jest test suite
  - Ensure `yarn start` includes those

- Define Pointer family of TypeScript types
  - Use namespaces to reflect schema hierarchy (Pointer.Region, etc.)
  - Provide type guards for detecting conformance to these types

- Define Machine and Machine.State interfaces to adapt external EVMs

- Define Cursor interface for inspecting machine state for a pointer
  - Include `view(state: Machine.State): Promise<Cursor.Regions>`
  - Define Cursor.Region as a region with all expressions evaluated
  - Define Cursor.Regions as an array-like collection of regions

- Implement read functionality for a Cursor.Region

- Implement evaluate functionality for a Pointer.Expression

- Implement dereference functionality to get a Cursor from a Pointer

- Include integration tests that use solc and Ganache to ensure expected
  pointer changes occur over the lifetime of an actual transaction
  • Loading branch information
gnidan committed Jun 17, 2024
1 parent 60d7296 commit bc2579f
Show file tree
Hide file tree
Showing 35 changed files with 4,144 additions and 646 deletions.
3 changes: 2 additions & 1 deletion bin/start
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ else
fi

# Run the commands with concurrently
concurrently --names=format,web,jest \
concurrently --names=format,pointers,web,jest \
"cd ./packages/format && yarn watch" \
"cd ./packages/pointers && yarn watch" \
"cd ./packages/web && yarn start $NO_OPEN_FLAG" \
"yarn test --watchAll"

2 changes: 2 additions & 0 deletions packages/pointers/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules
dist
7 changes: 7 additions & 0 deletions packages/pointers/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# @ethdebug/pointers

_This NPM package contains a reference implementation for dereferencing
**ethdebug/format** [pointers](https://ethdebug.github.io/format/spec/pointer/overview)._

:warning: This package is currently unpublished until ethdebug/format is more
complete.
21 changes: 21 additions & 0 deletions packages/pointers/jest.config.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
preset: "ts-jest",
testEnvironment: "node",
extensionsToTreatAsEsm: [".ts"],
moduleFileExtensions: ["ts", "js"],
moduleNameMapper: {
'^(\\.{1,2}/.*)\\.js$': '$1',
},
modulePathIgnorePatterns: ["<rootDir>/dist/"],
transform: {
// '^.+\\.[tj]sx?$' to process js/ts with `ts-jest`
// '^.+\\.m?[tj]sx?$' to process js/ts/mjs/mts with `ts-jest`
'^.+\\.tsx?$': [
'ts-jest',
{
useESM: true,
},
],
},
};
28 changes: 28 additions & 0 deletions packages/pointers/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"name": "@ethdebug/pointers",
"version": "0.1.0-0",
"description": "Reference implementation for ethdebug/format pointers",
"main": "dist/src/index.js",
"type": "module",
"license": "MIT",
"scripts": {
"prepare": "tsc",
"watch": "yarn prepare --watch",
"test": "node --experimental-vm-modules $(yarn bin jest)"
},
"devDependencies": {
"@ethdebug/format": "^0.1.0-0",
"@jest/globals": "^29.7.0",
"chalk": "^5.3.0",
"cli-highlight": "^2.1.11",
"ganache": "7.9.x",
"jest": "^29.7.0",
"solc": "^0.8.26",
"ts-jest": "^29.1.1",
"ts-node": "^10.9.2",
"typescript": "^5.3.3"
},
"dependencies": {
"ethereum-cryptography": "^2.1.3"
}
}
32 changes: 32 additions & 0 deletions packages/pointers/src/cursor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import type { Machine } from "./machine.js";
import type { Pointer } from "./pointer.js";
import type { Data } from "./data.js";

export interface Cursor {
view(state: Machine.State): Promise<Cursor.View>;
}

export namespace Cursor {
export interface View {
read(region: Cursor.Region): Promise<Data>;
regions: Regions
}

export type Regions =
& Cursor.Region[]
& { [name: string]: Cursor.Region; }
& {
named(name: string): Cursor.Region[];
lookup: { [name: string]: Cursor.Region };
};

export type Region<R extends Pointer.Region = Pointer.Region> = {
[K in keyof R]: K extends "slot" | "offset" | "length"
? R[K] extends Pointer.Expression
? Data
: R[K] extends Pointer.Expression | undefined
? Data | undefined
: R[K]
: R[K];
}
}
160 changes: 160 additions & 0 deletions packages/pointers/src/data.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
import { expect, describe, it } from "@jest/globals";

import { Data } from "./data.js";

describe("Data", () => {
describe(".prototype.asUint()", () => {
it("correctly converts to integers (big endian)", () => {
const data = new Data([0x01, 0x00]);

expect(`${data.asUint()}`).toBe("256");
});
});

describe(".fromUint()", () => {
it("correctly creates Data instances from bigints", () => {
const data1 = Data.fromUint(0n);
expect(data1).toEqual(new Data([]));

const data2 = Data.fromUint(255n);
expect(data2).toEqual(new Data([0xff]));

const data3 = Data.fromUint(256n);
expect(data3).toEqual(new Data([0x01, 0x00]));

const data4 = Data.fromUint(1234567890n);
expect(data4).toEqual(new Data([0x49, 0x96, 0x02, 0xd2]));
});
});

describe(".fromNumber()", () => {
it("correctly creates Data instances from numbers", () => {
const data1 = Data.fromNumber(0);
expect(data1).toEqual(Data.zero());

const data2 = Data.fromNumber(255);
expect(data2).toEqual(new Data([0xff]));

const data3 = Data.fromNumber(256);
expect(data3).toEqual(new Data([0x01, 0x00]));
});
});

describe(".fromHex()", () => {
it("correctly creates Data instances from hex strings", () => {
const data1 = Data.fromHex("0x00");
expect(data1).toEqual(new Data([0x00]));

const data2 = Data.fromHex("0xff");
expect(data2).toEqual(new Data([0xff]));

const data3 = Data.fromHex("0x0100");
expect(data3).toEqual(new Data([0x01, 0x00]));

const data4 = Data.fromHex("0x499602d2");
expect(data4).toEqual(new Data([0x49, 0x96, 0x02, 0xd2]));
});

it("throws an error for invalid hex string format", () => {
expect(() => Data.fromHex("ff")).toThrow("Invalid hex string format. Expected \"0x\" prefix.");
});
});
});

describe("Word", () => {
describe(".prototype.asUint", () => {
it("correctly converts to integers (big endian)", () => {
const word = new Data.Word([
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
]);

expect(`${word.asUint()}`).toBe("256");
});
});

describe(".fromUint()", () => {
it("correctly creates Word instances from BigInt values", () => {
const word1 = Data.Word.fromUint(0n);
expect(word1).toEqual(Data.Word.zero());

const word2 = Data.Word.fromUint(255n);
expect(word2).toEqual(new Data.Word([
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff,
]));

const word3 = Data.Word.fromUint(256n);
expect(word3).toEqual(new Data.Word([
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
]));

const word4 = Data.Word.fromUint(1234567890n);
expect(word4).toEqual(new Data.Word([
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x49, 0x96, 0x02, 0xd2,
]));
});
});

describe(".fromNumber()", () => {
it("correctly creates Word instances from unsigned integers", () => {
const word1 = Data.Word.fromNumber(0);
expect(word1).toEqual(Data.Word.zero());

const word2 = Data.Word.fromNumber(255);
expect(word2).toEqual(new Data.Word([
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff,
]));

const word3 = Data.Word.fromNumber(256);
expect(word3).toEqual(new Data.Word([
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
]));

});
});

describe(".fromHex()", () => {
it("correctly creates Word instances from hex strings", () => {
const word1 = Data.Word.fromHex(
"0x0000000000000000000000000000000000000000000000000000000000000000"
);
expect(word1).toEqual(Data.Word.zero());

const word2 = Data.Word.fromHex(
"0x00000000000000000000000000000000000000000000000000000000000000ff"
);
expect(word2).toEqual(new Data.Word([
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff,
]));

const word3 = Data.Word.fromHex("0x0000000000000000000000000000000000000000000000000000000000000100");
expect(word3).toEqual(new Data.Word([
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
]));
});
});
});

Loading

0 comments on commit bc2579f

Please sign in to comment.