Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions src/assets/traces-example.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{
"$schema": "./traces-schema.json",
"initial-pc": 5,
"initial-gas": "0x1234",
"initial-args": "0x1234",
"expected-gas": "0x0",
"expected-status": "halt",
"spi-program": "0x1234",
"host-calls-trace": [
{
"ecalli": 10,
"pc": 1234,
"before": {
"gas": "0x1234",
"regs": {
"7": "0x1234"
},
"memory": [
{
"address": 65539,
"contents": "0x04"
}
]
},
"after": {
"gas": "0x1230",
"regs": {
"7": "0x1234"
},
"memory": [
{
"address": 65538,
"contents": "0x04"
}
]
}
}
]
}
113 changes: 113 additions & 0 deletions src/assets/traces-schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "PVM Debugger Trace",
"description": "Schema for PVM debugger trace files",
"type": "object",
"required": ["initial-pc", "initial-gas", "initial-args", "host-calls-trace"],
"properties": {
"initial-pc": {
"type": "integer",
"description": "Initial program counter value"
},
"initial-gas": {
"type": "string",
"pattern": "^0x[0-9a-fA-F]+$",
"description": "Initial gas amount as hexadecimal string (u64)"
},
"initial-args": {
"type": "string",
"pattern": "^0x[0-9a-fA-F]+$",
"description": "Initial arguments as hexadecimal string (bytes)"
},
"expected-gas": {
"type": "string",
"pattern": "^0x[0-9a-fA-F]+$",
"description": "Expected remaining gas as hexadecimal string (u64)"
},
"expected-status": {
"type": "string",
"enum": ["panic", "halt", "page-fault"],
"description": "Expected program execution status"
},
"spi-program": {
"type": "string",
"pattern": "^0x[0-9a-fA-F]+$",
"description": "SPI program data as hexadecimal string (bytes)"
},
"host-calls-trace": {
"type": "array",
"description": "Array of host call trace entries",
"items": {
"type": "object",
"required": ["after"],
"properties": {
"ecalli": {
"type": "integer",
"description": "ECALLI instruction identifier"
},
"pc": {
"type": "integer",
"description": "Program counter value at the time of the call"
},
"before": {
"$ref": "#/definitions/vmState",
"description": "VM state before the host call"
},
"after": {
"$ref": "#/definitions/vmState",
"description": "VM state after the host call"
}
},
"additionalProperties": false
}
}
},
"additionalProperties": false,
"definitions": {
"vmState": {
"type": "object",
"required": ["gas", "regs", "memory"],
"properties": {
"gas": {
"type": "string",
"pattern": "^0x[0-9a-fA-F]+$",
"description": "Gas amount as hexadecimal string (u64)"
},
"regs": {
"type": "object",
"patternProperties": {
"^[0-9]+$": {
"type": "string",
"pattern": "^0x[0-9a-fA-F]+$",
"description": "Register value as hexadecimal string (u64)"
}
},
"additionalProperties": false,
"description": "Register values indexed by register number"
},
"memory": {
"type": "array",
"description": "Memory contents at specific addresses",
"items": {
"type": "object",
"required": ["address", "contents"],
"properties": {
"address": {
"type": "integer",
"minimum": 0,
"description": "Memory address"
},
"contents": {
"type": "string",
"pattern": "^0x[0-9a-fA-F]+$",
"description": "Memory contents as hexadecimal string (bytes)"
}
},
"additionalProperties": false
}
}
},
"additionalProperties": false
}
}
}
6 changes: 4 additions & 2 deletions src/components/DebuggerSettings/Content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import { logger } from "@/utils/loggerService";
import { ToggleDarkMode } from "@/packages/ui-kit/DarkMode/ToggleDarkMode";
import { Separator } from "../ui/separator";
import { WithHelp } from "../WithHelp/WithHelp";
import { TracesFileManager } from "../TracesFileManager";
import { cn } from "@/lib/utils";

function stringToNumber<T>(value: string, cb: (x: string) => T): T {
try {
Expand Down Expand Up @@ -105,15 +107,15 @@ export const DebuggerSettingsContent = () => {
</WithHelp>
</span>

<div className="flex">TODO</div>
<TracesFileManager />
</div>

<div className="p-4 mt-2 flex justify-between items-center mb-4">
<span className="block text-xs font-bold">
<WithHelp help="Hex-encoded JAM SPI arguments written to the heap">JAM SPI arguments</WithHelp>
</span>
<Input
className={commonClass}
className={cn("flex-1", commonClass)}
placeholder="0x-prefixed, encoded operands"
onChange={(e) => {
const value = e.target?.value;
Expand Down
5 changes: 5 additions & 0 deletions src/components/Header/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Header as FluffyHeader } from "@/packages/ui-kit/Header";
import { DebuggerSettings } from "../DebuggerSettings";
import { PvmSelect } from "../PvmSelect";
import { NumeralSystemSwitch } from "../NumeralSystemSwitch";
import { HostCallStatus } from "../HostCallStatus";
import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuSeparator } from "../ui/dropdown-menu";
import { Button } from "../ui/button";
import { EllipsisVertical } from "lucide-react";
Expand All @@ -13,6 +14,10 @@ const EndSlot = () => {

return (
<div className="text-white flex w-full justify-end">
<div className="flex items-center mr-4">
<HostCallStatus />
</div>

<NumeralSystemSwitch className="hidden md:flex ml-7 mr-4" />

<div className="w-full md:max-w-[350px] flex items-center ml-3">
Expand Down
46 changes: 46 additions & 0 deletions src/components/HostCallStatus/HostCallStatus.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React from "react";
import { useAppSelector } from "@/store/hooks";
import { selectHostCallsTrace, selectCurrentHostCallIndex } from "@/store/debugger/debuggerSlice";
import { Activity, CheckCircle } from "lucide-react";

export const HostCallStatus: React.FC = () => {
const tracesFile = useAppSelector(selectHostCallsTrace);
const currentIndex = useAppSelector(selectCurrentHostCallIndex);

if (!tracesFile || tracesFile["host-calls-trace"].length === 0) {
return null;
}

const totalHostCalls = tracesFile["host-calls-trace"].length;
const isComplete = currentIndex >= totalHostCalls;
const progress = Math.min((currentIndex / totalHostCalls) * 100, 100);

return (
<div className="flex items-center gap-2 text-xs text-muted-foreground">
<div className="flex items-center gap-1">
{isComplete ? (
<CheckCircle className="w-4 h-4 text-green-500" />
) : (
<Activity className="w-4 h-4 text-blue-500" />
)}
<span className="font-medium">
Host Calls: {currentIndex}/{totalHostCalls}
</span>
</div>

{/* Progress bar */}
<div className="w-16 h-2 bg-muted rounded-full overflow-hidden">
<div
className="h-full transition-all duration-300 ease-out rounded-full"
style={{
width: `${progress}%`,
backgroundColor: isComplete ? "#22c55e" : "#3b82f6",
}}
/>
</div>

{/* Status text */}
<span className="text-[10px]">{isComplete ? "Complete" : "In Progress"}</span>
</div>
);
};
1 change: 1 addition & 0 deletions src/components/HostCallStatus/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { HostCallStatus } from "./HostCallStatus";
37 changes: 35 additions & 2 deletions src/components/ProgramLoader/Loader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { Separator } from "../ui/separator";
import { TriangleAlert } from "lucide-react";
import { WithHelp } from "../WithHelp/WithHelp";
import { Input } from "../ui/input";
import { TracesFileManager } from "../TracesFileManager";

export const Loader = ({ setIsDialogOpen }: { setIsDialogOpen?: (val: boolean) => void }) => {
const dispatch = useAppDispatch();
Expand All @@ -22,6 +23,7 @@ export const Loader = ({ setIsDialogOpen }: { setIsDialogOpen?: (val: boolean) =
const debuggerActions = useDebuggerActions();
const isLoading = useAppSelector(selectIsAnyWorkerLoading);
const debuggerState = useAppSelector((state) => state.debugger);

const navigate = useNavigate();

useEffect(() => {
Expand All @@ -35,7 +37,14 @@ export const Loader = ({ setIsDialogOpen }: { setIsDialogOpen?: (val: boolean) =
dispatch(setIsProgramEditMode(false));

try {
await debuggerActions.handleProgramLoad(program || programLoad);
const loadedProgram = program || programLoad;

// Load traces file if present
if (loadedProgram?.tracesFile) {
debuggerActions.handleTracesLoad(loadedProgram.tracesFile);
}

await debuggerActions.handleProgramLoad(loadedProgram);
setIsDialogOpen?.(false);
navigate("/", { replace: true });
} catch (error) {
Expand Down Expand Up @@ -110,8 +119,32 @@ export const Loader = ({ setIsDialogOpen }: { setIsDialogOpen?: (val: boolean) =
<span className="block text-xs font-bold min-w-[150px]">
<WithHelp help="JSON containing instructions how to handle host calls">Host Calls Trace</WithHelp>
</span>
<p className="flex-1 ml-2">(coming soon)</p>
<div className="flex-1 ml-2">
{programLoad.tracesFile ? (
<span className="text-xs">{programLoad.tracesFile["host-calls-trace"].length} host call(s)</span>
) : (
<TracesFileManager compact inlineClear />
)}
</div>
</div>
</>
)}
{programLoad.tracesFile && (
<>
<div className="mt-2 flex justify-between items-center">
<span className="block text-xs font-bold min-w-[150px]">Initial PC:</span>
<code className="flex-1 ml-2">{programLoad.tracesFile["initial-pc"]}</code>
</div>
<div className="mt-2 flex justify-between items-center">
<span className="block text-xs font-bold min-w-[150px]">Initial Gas:</span>
<code className="flex-1 ml-2">{programLoad.tracesFile["initial-gas"]}</code>
</div>
{programLoad.tracesFile["expected-status"] && (
<div className="mt-2 flex justify-between items-center">
<span className="block text-xs font-bold min-w-[150px]">Expected Status:</span>
<code className="flex-1 ml-2">{programLoad.tracesFile["expected-status"]}</code>
</div>
)}
</>
)}
</div>
Expand Down
Loading
Loading