-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement pointer dereferencing in TypeScript
- 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
Showing
35 changed files
with
4,144 additions
and
646 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
node_modules | ||
dist |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
}, | ||
], | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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]; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
])); | ||
}); | ||
}); | ||
}); | ||
|
Oops, something went wrong.