UNPKG

@polareth/evmstate

Version:

A TypeScript library for tracing, and visualizing EVM state changes with detailed human-readable labeling.

339 lines (302 loc) 13.7 kB
import type { Hex } from "tevm"; import type { AbiType, AbiTypeToPrimitiveType, SolidityAddress, SolidityBool, SolidityInt } from "abitype"; import type { SolcStorageLayoutTypes } from "@/lib/solc.js"; /* -------------------------------------------------------------------------- */ /* TYPE HELPERS */ /* -------------------------------------------------------------------------- */ /** Makes all properties of an object readonly deeply */ export type DeepReadonly<T> = T extends (infer R)[] ? ReadonlyArray<DeepReadonly<R>> : T extends Function ? T : T extends object ? { readonly [K in keyof T]: DeepReadonly<T[K]> } : T; /** Extract the final value ABI type from a solc type ID */ export type ParseSolidityType< TTypeId extends string, TTypes extends SolcStorageLayoutTypes, > = TTypeId extends keyof TTypes ? TTypes[TTypeId] extends { label: infer Label extends string } ? Label : never : TTypeId; /* -------------------------------------------------------------------------- */ /* TYPE EXTRACTION UTILITIES */ /* -------------------------------------------------------------------------- */ /** Extract key type from a mapping declaration */ export type ExtractMappingKeyType<TTypeLabel extends string> = TTypeLabel extends `mapping(${infer KeyType} => ${string})` ? KeyType : never; /** Extract value type from a mapping declaration */ export type ExtractMappingValueType<TTypeLabel extends string> = TTypeLabel extends `mapping(${string} => ${infer ValueType})` ? ValueType : never; /** Extract base type from an array declaration */ export type ExtractArrayBaseType<TTypeLabel extends string> = TTypeLabel extends | `${infer BaseType}[]` | `${infer BaseType}[${string}]` ? BaseType : never; /** Extract struct member names from a struct type */ export type ExtractStructMembers<StructName extends string, TTypes extends Record<string, any>> = { [TTypeId in keyof TTypes]: TTypes[TTypeId] extends { label: infer Label extends string } ? Label extends `struct ${StructName}` ? TTypes[TTypeId] extends { members: readonly any[] } ? TTypes[TTypeId]["members"][number]["label"] : never : never : never; }[keyof TTypes]; /** Helper type to get the type of a struct member */ export type ExtractStructMemberType< StructName extends string, MemberName extends string, TTypes extends SolcStorageLayoutTypes, > = { [TTypeId in keyof TTypes]: TTypes[TTypeId] extends { label: infer Label extends string } ? Label extends `struct ${StructName}` ? TTypes[TTypeId] extends { members: readonly any[] } ? { [MemberIndex in keyof TTypes[TTypeId]["members"]]: TTypes[TTypeId]["members"][MemberIndex] extends { label: infer MLabel extends string; } ? MLabel extends MemberName ? ParseSolidityType< TTypes[TTypeId]["members"][MemberIndex] extends { type: infer T extends string } ? T : never, TTypes > : never : never; }[keyof TTypes[TTypeId]["members"]] : never : never : never; }[keyof TTypes]; /* -------------------------------------------------------------------------- */ /* STORAGE DATA TYPE MAPPING */ /* -------------------------------------------------------------------------- */ /** * Map Solidity types to TypeScript return types * * This is an opinionated way to retrieve primitive types, e.g. uint256 -> bigint but uint256[] -> bigint as well. A * struct returns the type of any member. It always traverses until the very last primitive type. */ export type SolidityTypeToTsType<TAbiType extends string, TTypes extends SolcStorageLayoutTypes> = // Handle primitive TTypes TAbiType extends AbiTypeInplace ? AbiTypeToPrimitiveType<TAbiType> : // Handle mappings (return value type) TAbiType extends `mapping(${string} => ${string})` ? SolidityTypeToTsType<ExtractMappingValueType<TAbiType>, TTypes> : // Handle arrays TAbiType extends `${string}[]` | `${string}[${string}]` ? SolidityTypeToTsType<ExtractArrayBaseType<TAbiType>, TTypes> | bigint // Add bigint for array length : // Handle struct TTypes - use a union of all member TTypes TAbiType extends `struct ${infer StructName}` ? ExtractStructMembers<StructName, TTypes> extends infer Members ? Members extends string ? SolidityTypeToTsType<ExtractStructMemberType<StructName, Members, TTypes> & string, TTypes> : unknown : unknown : // Handle bytes/string TAbiType extends "bytes" | "string" ? string | bigint // Add bigint for length : // Default case unknown; /* -------------------------------------------------------------------------- */ /* MAPPING TYPE HELPERS */ /* -------------------------------------------------------------------------- */ /** The following types are not used in the library, but can be useful as utility types */ /** Extract mapping key TTypes with their corresponding TypeScript TTypes */ export type GetMappingKeyTypePairs< TTypeLabel extends string, TTypes extends SolcStorageLayoutTypes, Result extends readonly [string, any][] = [], > = TTypeLabel extends `mapping(${infer KeyType} => ${infer ValueType})` ? ValueType extends `mapping(${string} => ${string})` ? GetMappingKeyTypePairs< ValueType, TTypes, [...Result, [KeyType, KeyType extends AbiType ? AbiTypeToPrimitiveType<KeyType> : never]] > : [...Result, [KeyType, KeyType extends AbiType ? AbiTypeToPrimitiveType<KeyType> : never]] : Result; /** Get just the Solidity type strings for mapping keys as a tuple */ export type GetMappingKeyTypes<TTypeLabel extends string, TTypes extends SolcStorageLayoutTypes> = GetMappingKeyTypePairs<TTypeLabel, TTypes> extends readonly [...infer Pairs] ? { [K in keyof Pairs]: Pairs[K] extends [infer SolType, any] ? SolType : never } : []; /** Get just the TypeScript types for mapping keys as a tuple */ export type GetMappingKeyTsTypes<TTypeLabel extends string, TTypes extends SolcStorageLayoutTypes> = GetMappingKeyTypePairs<TTypeLabel, TTypes> extends readonly [...infer Pairs] ? { [K in keyof Pairs]: Pairs[K] extends [string, infer TsType] ? TsType : never } : []; /** * Create a tuple type of mapping keys with their types (for display/debugging) Each element has both type and value * properties */ export type GetMappingKeysTuple<TTypeLabel extends string, TTypes extends SolcStorageLayoutTypes> = GetMappingKeyTypePairs<TTypeLabel, TTypes> extends readonly [...infer Pairs] ? { [K in keyof Pairs]: Pairs[K] extends [infer SolType, infer TsType] ? { type: SolType; value: TsType } : never; } : []; /* -------------------------------------------------------------------------- */ /* DECODED RESULTS */ /* -------------------------------------------------------------------------- */ export enum TypePriority { Primitive = 0, Struct = 1, Bytes = 2, StaticArray = 3, DynamicArray = 4, Mapping = 5, } export enum PathSegmentKind { StructField = "struct_field", ArrayIndex = "array_index", MappingKey = "mapping_key", ArrayLength = "array_length", BytesLength = "bytes_length", } /** Create a path segment for a struct field */ export type StructFieldSegment<TStructName extends string, TTypes extends SolcStorageLayoutTypes> = { kind: PathSegmentKind.StructField; name: ExtractStructMembers<TStructName, TTypes>; }; /** Create a path segment for an array index */ export type ArrayIndexSegment = { kind: PathSegmentKind.ArrayIndex; index: bigint; }; /** Create a path segment for an array _length */ export type ArrayLengthSegment = { kind: PathSegmentKind.ArrayLength; name: "_length"; }; /** Create a path segment for bytes/string length */ export type BytesLengthSegment = { kind: PathSegmentKind.BytesLength; name: "_length"; }; /** Create a path segment for a mapping key */ export type MappingKeySegment<TKeyType extends string, TTypes extends SolcStorageLayoutTypes> = { kind: PathSegmentKind.MappingKey; key: SolidityTypeToTsType<TKeyType, TTypes>; keyType: TKeyType; }; /** * A type that builds path segments incrementally for any Solidity type. * * It handles one "layer" at a time and recursively processes inner TTypes */ export type PathBuilder< TTypeLabel extends string, TTypes extends SolcStorageLayoutTypes, BasePath extends readonly PathSegment[] = [], > = // Base case - if we've reached a primitive type, return the accumulated path TTypeLabel extends AbiTypeInplace ? BasePath : // Handle mapping TTypes - add mapping key segment and recurse with value type TTypeLabel extends `mapping(${infer KeyType} => ${infer ValueType})` ? PathBuilder< ValueType, TTypes, [ ...BasePath, { kind: PathSegmentKind.MappingKey; key: KeyType extends AbiTypeInplace ? AbiTypeToPrimitiveType<KeyType> : never; keyType: KeyType extends AbiTypeInplace ? KeyType : never; }, ] > : // Handle array TTypes - add array index segment and recurse with base type TTypeLabel extends `${infer BaseType}[]` | `${infer BaseType}[${string}]` ? PathBuilder<BaseType, TTypes, [...BasePath, ArrayIndexSegment]> | [...BasePath, ArrayLengthSegment] : // Handle struct TTypes - distribute over members, add struct field segment, and recurse with member type TTypeLabel extends `struct ${infer StructName}` ? { [Member in ExtractStructMembers<StructName, TTypes> & string]: PathBuilder< ExtractStructMemberType<StructName, Member, TTypes> & string, TTypes, [...BasePath, { kind: PathSegmentKind.StructField; name: Member }] >; }[ExtractStructMembers<StructName, TTypes> & string] : // Handle bytes/string TTypes - add bytes length segment TTypeLabel extends "bytes" | "string" ? BasePath | [...BasePath, BytesLengthSegment] : // Default case - unknown type, return base path BasePath; /** Get all possible path segments for a variable type */ export type VariablePathSegments<TTypeLabel extends string, TTypes extends SolcStorageLayoutTypes> = PathBuilder< TTypeLabel, TTypes >; /** * Generate a full expression from a variable name and path segments This type correctly handles all path segments in * the correct order */ export type FullExpression<Name extends string, Path extends any[]> = Path extends [] ? Name : Path extends [infer First, ...infer Rest] ? FullExpression<`${Name}${PathSegmentToString<First>}`, Rest extends any[] ? Rest : []> : Name; /** Convert a path segment to its string representation */ type PathSegmentToString<TSegment> = TSegment extends { kind: PathSegmentKind.MappingKey; key: infer Key; } ? `[${Key & (string | number | bigint | boolean)}]` : TSegment extends { kind: PathSegmentKind.ArrayLength } ? `._length` : TSegment extends { kind: PathSegmentKind.BytesLength } ? `._length` : TSegment extends { kind: PathSegmentKind.ArrayIndex; index: infer Index } ? `[${Index & (string | number | bigint)}]` : TSegment extends { kind: PathSegmentKind.StructField; name: infer Name } ? `.${Name & string}` : never; /** Generate a full expression for a variable */ export type VariableExpression< TName extends string, TTypeLabel extends string, TTypes extends SolcStorageLayoutTypes, > = FullExpression<TName, VariablePathSegments<TTypeLabel, TTypes>>; /** Generic path segment type */ export type PathSegment = | { kind: PathSegmentKind.StructField; name: string } | { kind: PathSegmentKind.ArrayIndex; index: bigint } | { kind: PathSegmentKind.MappingKey; key: AbiTypeToPrimitiveType<AbiTypeInplace>; keyType: AbiTypeInplace; } | { kind: PathSegmentKind.ArrayLength; name: "_length" } | { kind: PathSegmentKind.BytesLength; name: "_length" }; /* -------------------------------------------------------------------------- */ /* INTERNAL TTypes */ /* -------------------------------------------------------------------------- */ /** Mapping key type */ export interface MappingKey<TAbiType extends AbiType = AbiType> { // Value padded to 32 bytes hex: Hex; // Type of the value if known type?: TAbiType; // Decoded value if known decoded?: AbiTypeToPrimitiveType<TAbiType>; } /** A subset of AbiType with only inplace TTypes */ export type AbiTypeInplace = SolidityAddress | SolidityBool | SolidityInt | `bytes${MBytes}`; // We need our own type as we can't omit "bytes" and use AbiTypeInplace as a subset of AbiType // prettier-ignore type MBytes = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32; export type DecodedResult = { name: string; type: string; current: { hex: Hex; decoded?: unknown }; next?: { hex: Hex; decoded?: unknown }; slots: Array<Hex>; path: Array<PathSegment>; note?: string; };