From 3461c5b49a1e87256aa8f2e1b34b60ae69f00c7c Mon Sep 17 00:00:00 2001 From: Anastasia Rodionova Date: Thu, 28 Mar 2024 14:23:11 +0100 Subject: [PATCH 1/6] Remove unused components --- apps/web/src/components/ui/code-viewer.tsx | 89 ------------ apps/web/src/components/ui/command.tsx | 155 --------------------- apps/web/src/components/ui/dialog.tsx | 123 ---------------- apps/web/src/components/ui/popover.tsx | 31 ----- 4 files changed, 398 deletions(-) delete mode 100644 apps/web/src/components/ui/code-viewer.tsx delete mode 100644 apps/web/src/components/ui/command.tsx delete mode 100644 apps/web/src/components/ui/dialog.tsx delete mode 100644 apps/web/src/components/ui/popover.tsx diff --git a/apps/web/src/components/ui/code-viewer.tsx b/apps/web/src/components/ui/code-viewer.tsx deleted file mode 100644 index 8e6e3310..00000000 --- a/apps/web/src/components/ui/code-viewer.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import { Button } from "@/components/ui/button"; -import { - Dialog, - DialogContent, - DialogDescription, - DialogHeader, - DialogTitle, - DialogTrigger, -} from "@/components/ui/dialog"; - -export function CodeViewer() { - return ( - - - - - - - View code - - You can use the following code to start integrating your current - prompt and settings into your application. - - -
-
-
-              
-                
-                  import os
-                
-                
-                  import openai
-                
-                
-                
-                  openai.api_key = os.getenv(
-                  
-                    "OPENAI_API_KEY"
-                  
-                  )
-                
-                
-                response = openai.Completion.create(
-                
-                  {" "}
-                  model=
-                  "davinci",
-                
-                
-                  {" "}
-                  prompt="",
-                
-                
-                  {" "}
-                  temperature=0.9,
-                
-                
-                  {" "}
-                  max_tokens=5,
-                
-                
-                  {" "}
-                  top_p=1,
-                
-                
-                  {" "}
-                  frequency_penalty=0,
-                
-                
-                  {" "}
-                  presence_penalty=0,
-                
-                )
-              
-            
-
-
-

- Your API Key can be found here. You should use environment - variables or a secret management tool to expose your key to your - applications. -

-
-
-
-
- ); -} diff --git a/apps/web/src/components/ui/command.tsx b/apps/web/src/components/ui/command.tsx deleted file mode 100644 index ee27a1a5..00000000 --- a/apps/web/src/components/ui/command.tsx +++ /dev/null @@ -1,155 +0,0 @@ -"use client"; - -import * as React from "react"; -import { DialogProps } from "@radix-ui/react-dialog"; -import { Command as CommandPrimitive } from "cmdk"; -import { Search } from "lucide-react"; - -import { cn } from "@/lib/utils"; -import { Dialog, DialogContent } from "@/components/ui/dialog"; - -const Command = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -Command.displayName = CommandPrimitive.displayName; - -interface CommandDialogProps extends DialogProps {} - -const CommandDialog = ({ children, ...props }: CommandDialogProps) => { - return ( - - - - {children} - - - - ); -}; - -const CommandInput = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( -
- - -
-)); - -CommandInput.displayName = CommandPrimitive.Input.displayName; - -const CommandList = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); - -CommandList.displayName = CommandPrimitive.List.displayName; - -const CommandEmpty = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->((props, ref) => ( - -)); - -CommandEmpty.displayName = CommandPrimitive.Empty.displayName; - -const CommandGroup = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); - -CommandGroup.displayName = CommandPrimitive.Group.displayName; - -const CommandSeparator = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -CommandSeparator.displayName = CommandPrimitive.Separator.displayName; - -const CommandItem = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); - -CommandItem.displayName = CommandPrimitive.Item.displayName; - -const CommandShortcut = ({ - className, - ...props -}: React.HTMLAttributes) => { - return ( - - ); -}; -CommandShortcut.displayName = "CommandShortcut"; - -export { - Command, - CommandDialog, - CommandInput, - CommandList, - CommandEmpty, - CommandGroup, - CommandItem, - CommandShortcut, - CommandSeparator, -}; diff --git a/apps/web/src/components/ui/dialog.tsx b/apps/web/src/components/ui/dialog.tsx deleted file mode 100644 index aca10680..00000000 --- a/apps/web/src/components/ui/dialog.tsx +++ /dev/null @@ -1,123 +0,0 @@ -"use client"; - -import * as React from "react"; -import * as DialogPrimitive from "@radix-ui/react-dialog"; -import { X } from "lucide-react"; - -import { cn } from "@/lib/utils"; - -const Dialog = DialogPrimitive.Root; - -const DialogTrigger = DialogPrimitive.Trigger; - -const DialogPortal = ({ - className, - ...props -}: DialogPrimitive.DialogPortalProps) => ( - -); -DialogPortal.displayName = DialogPrimitive.Portal.displayName; - -const DialogOverlay = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -DialogOverlay.displayName = DialogPrimitive.Overlay.displayName; - -const DialogContent = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, children, ...props }, ref) => ( - - - - {children} - - - Close - - - -)); -DialogContent.displayName = DialogPrimitive.Content.displayName; - -const DialogHeader = ({ - className, - ...props -}: React.HTMLAttributes) => ( -
-); -DialogHeader.displayName = "DialogHeader"; - -const DialogFooter = ({ - className, - ...props -}: React.HTMLAttributes) => ( -
-); -DialogFooter.displayName = "DialogFooter"; - -const DialogTitle = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -DialogTitle.displayName = DialogPrimitive.Title.displayName; - -const DialogDescription = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -DialogDescription.displayName = DialogPrimitive.Description.displayName; - -export { - Dialog, - DialogTrigger, - DialogContent, - DialogHeader, - DialogFooter, - DialogTitle, - DialogDescription, -}; diff --git a/apps/web/src/components/ui/popover.tsx b/apps/web/src/components/ui/popover.tsx deleted file mode 100644 index 73be7bb1..00000000 --- a/apps/web/src/components/ui/popover.tsx +++ /dev/null @@ -1,31 +0,0 @@ -"use client"; - -import * as React from "react"; -import * as PopoverPrimitive from "@radix-ui/react-popover"; - -import { cn } from "@/lib/utils"; - -const Popover = PopoverPrimitive.Root; - -const PopoverTrigger = PopoverPrimitive.Trigger; - -const PopoverContent = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( - - - -)); -PopoverContent.displayName = PopoverPrimitive.Content.displayName; - -export { Popover, PopoverTrigger, PopoverContent }; From cb8257f4600d3382c171a4efd26fb18a63c44e50 Mon Sep 17 00:00:00 2001 From: Anastasia Rodionova Date: Sun, 31 Mar 2024 18:58:01 +0200 Subject: [PATCH 2/6] Use js code for interpretation --- packages/transaction-decoder/package.json | 5 +- .../src/interpreters/index.ts | 59 ++++++++----------- .../src/interpreters/vm.ts | 25 ++++++++ packages/transaction-decoder/src/types.ts | 3 - 4 files changed, 53 insertions(+), 39 deletions(-) create mode 100644 packages/transaction-decoder/src/interpreters/vm.ts diff --git a/packages/transaction-decoder/package.json b/packages/transaction-decoder/package.json index 09ca6824..889a4054 100644 --- a/packages/transaction-decoder/package.json +++ b/packages/transaction-decoder/package.json @@ -42,8 +42,7 @@ "peerDependencies": { "@effect/schema": "^0.59.1", "effect": "^2.0.3", - "viem": "^2.7.22", - "jsonata": "^2.0.3" + "viem": "^2.7.22" }, "devDependencies": { "@effect/schema": "^0.59.1", @@ -57,7 +56,6 @@ "eslint": "^8.49.0", "eslint-config-custom": "workspace:*", "eslint-config-prettier": "^8.10.0", - "jsonata": "^2.0.3", "prettier": "^2.8.8", "ts-node": "^10.9.1", "tsconfig": "workspace:*", @@ -76,6 +74,7 @@ }, "sideEffects": false, "dependencies": { + "quickjs-emscripten": "^0.29.1", "traverse": "^0.6.7" } } \ No newline at end of file diff --git a/packages/transaction-decoder/src/interpreters/index.ts b/packages/transaction-decoder/src/interpreters/index.ts index c7c43c8b..4d2c2895 100644 --- a/packages/transaction-decoder/src/interpreters/index.ts +++ b/packages/transaction-decoder/src/interpreters/index.ts @@ -1,52 +1,46 @@ -import { sameAddress } from '../helpers/address.js' -import type { DecodedTx, Interpreter } from '../types.js' -import jsonata from 'jsonata' - -function findinterpretersForContract( - contractAddress: string, - chainID: number, - interpreters: Interpreter[], -): Interpreter[] { - return interpreters.filter( - (interpreter) => sameAddress(interpreter.contractAddress, contractAddress) && interpreter.chainID === chainID, - ) +import { DecodedTx, Interpreter } from '@/types.js' +import makeVM from './vm.js' + +async function runJSCode(input: string, code: string) { + const vm = await makeVM(Date.now() + 1000) + + vm.evalCode(code) + vm.runtime.executePendingJobs(-1) + + const result = vm.unwrapResult(vm.evalCode('transformEvent(' + input + ')')) + const ok = vm.dump(result) + + result.dispose() + vm.dispose() + + return ok } -export async function findInterpreter({ +export function findInterpreter({ decodedTx, interpreters, }: { decodedTx: DecodedTx interpreters: Interpreter[] -}): Promise { +}): Interpreter | undefined { try { - const contractAddress = decodedTx.toAddress - const chainID = decodedTx.chainID + const { toAddress: contractAddress, chainID } = decodedTx + if (!contractAddress) { return undefined } - const contractInterpreters = findinterpretersForContract(contractAddress, chainID, interpreters) + const id = `contract:${contractAddress},chain:${chainID}` - if (!contractInterpreters) { - return undefined - } + const contractTransformation = interpreters.find((interpreter) => interpreter.id === id) - for (const interpreter of contractInterpreters) { - const canInterpret = jsonata(interpreter.filter) - const canInterpretResult = await canInterpret.evaluate(decodedTx) - - if (!canInterpretResult) { - continue - } - return interpreter - } + return contractTransformation } catch (e) { - throw new Error(`Failed to find interpreter: ${e}`) + throw new Error(`Failed to find tx interpreter: ${e}`) } } -export async function runInterpreter({ +export async function applyInterpreter({ decodedTx, interpreter, }: { @@ -54,8 +48,7 @@ export async function runInterpreter({ interpreter: Interpreter }): Promise { try { - const expression = jsonata(interpreter.schema) - const result = await expression.evaluate(decodedTx) + const result = await runJSCode(JSON.stringify(decodedTx), interpreter.schema) return result } catch (e) { throw new Error(`Failed to run interpreter: ${e}`) diff --git a/packages/transaction-decoder/src/interpreters/vm.ts b/packages/transaction-decoder/src/interpreters/vm.ts new file mode 100644 index 00000000..44d2d354 --- /dev/null +++ b/packages/transaction-decoder/src/interpreters/vm.ts @@ -0,0 +1,25 @@ +import { getQuickJS, shouldInterruptAfterDeadline } from 'quickjs-emscripten' + +export default async function makeVM(timeout = -1) { + const QuickJS = await getQuickJS() + const vm = QuickJS.newContext() + if (timeout !== -1) { + vm.runtime.setInterruptHandler(shouldInterruptAfterDeadline(Date.now() + timeout)) + } + + // `console.log` + const logHandle = vm.newFunction('log', (...args) => { + const nativeArgs = args.map(vm.dump) + console.log('TxDecoder:', ...nativeArgs) + }) + + // Partially implement `console` object + const consoleHandle = vm.newObject() + vm.setProp(consoleHandle, 'log', logHandle) + vm.setProp(vm.global, 'console', consoleHandle) + + consoleHandle.dispose() + logHandle.dispose() + + return vm +} diff --git a/packages/transaction-decoder/src/types.ts b/packages/transaction-decoder/src/types.ts index 634849f9..64d19129 100644 --- a/packages/transaction-decoder/src/types.ts +++ b/packages/transaction-decoder/src/types.ts @@ -163,7 +163,4 @@ export interface InternalEvent { export interface Interpreter { id: string schema: string - filter: string - chainID: number - contractAddress?: string } From 21f2d853ed96b2d04eafb7f83fcf67ff9cc7664b Mon Sep 17 00:00:00 2001 From: Anastasia Rodionova Date: Sun, 31 Mar 2024 20:32:15 +0200 Subject: [PATCH 3/6] Add js interpreter in web playground --- apps/web/package.json | 7 +- apps/web/src/app/contract/[contract]/page.tsx | 2 +- .../web/src/app/contract/[contract]/table.tsx | 12 +- apps/web/src/app/layout.tsx | 20 +- apps/web/src/app/tx/[chainID]/[hash]/form.tsx | 145 ++--- .../src/app/tx/[chainID]/[hash]/loading.tsx | 73 +-- apps/web/src/app/tx/[chainID]/[hash]/page.tsx | 8 +- apps/web/src/components/ui/code-block.tsx | 44 ++ apps/web/src/lib/interpreter.ts | 134 ++--- pnpm-lock.yaml | 504 +++--------------- 10 files changed, 294 insertions(+), 655 deletions(-) create mode 100644 apps/web/src/components/ui/code-block.tsx diff --git a/apps/web/package.json b/apps/web/package.json index b3b7e81e..4950f134 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -11,17 +11,14 @@ }, "dependencies": { "@3loop/transaction-decoder": "workspace:*", + "@monaco-editor/react": "^4.6.0", "@prisma/client": "^5.2.0", - "@radix-ui/react-dialog": "^1.0.4", "@radix-ui/react-hover-card": "^1.0.6", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-label": "^2.0.2", - "@radix-ui/react-popover": "^1.0.6", "@radix-ui/react-select": "^2.0.0", "@radix-ui/react-separator": "^1.0.3", "@radix-ui/react-slot": "^1.0.2", - "@radix-ui/react-tabs": "^1.0.4", - "@tanstack/react-query": "^4.33.0", "@types/node": "^20.3.1", "@types/react": "18.2.21", "@types/react-dom": "18.2.7", @@ -29,12 +26,12 @@ "autoprefixer": "10.4.15", "class-variance-authority": "^0.7.0", "clsx": "^2.0.0", - "cmdk": "^0.2.0", "effect": "^2.0.3", "eslint": "^8.47.0", "eslint-config-next": "13.4.19", "jsonata": "^2.0.3", "lucide-react": "^0.274.0", + "monaco-editor": "^0.47.0", "next": "13.4.19", "postcss": "8.4.29", "react": "18.2.0", diff --git a/apps/web/src/app/contract/[contract]/page.tsx b/apps/web/src/app/contract/[contract]/page.tsx index adc1c7fa..3f199caa 100644 --- a/apps/web/src/app/contract/[contract]/page.tsx +++ b/apps/web/src/app/contract/[contract]/page.tsx @@ -39,7 +39,7 @@ export default async function Home({ return (
-
+
([]); - const [intepretors] = React.useState(getAvaliableinterpreters); + const [interpreters] = React.useState(getAvaliableinterpreters); React.useEffect(() => { async function run() { - if (intepretors == null) return; + if (interpreters == null) return; const withIntepretations = await Promise.all( txs.map((tx) => { - return findAndRunInterpreter(tx, intepretors); + return findAndRunInterpreter(tx, interpreters); }), ); setResult(withIntepretations); } run(); - }, [intepretors, txs]); + }, [interpreters, txs]); return ( diff --git a/apps/web/src/app/layout.tsx b/apps/web/src/app/layout.tsx index b13f7760..0d916c3d 100644 --- a/apps/web/src/app/layout.tsx +++ b/apps/web/src/app/layout.tsx @@ -4,8 +4,7 @@ import type { Metadata } from "next"; import { Inter } from "next/font/google"; import { MainNav } from "@/components/ui/main-nav"; import { Analytics } from "@vercel/analytics/react"; -import { aaveV2, DEFAULT_CHAIN_ID } from "../app/data"; -import { NetworkSelect } from "@/components/ui/network-select"; +import { aaveV2 } from "../app/data"; const navLinks = [ { @@ -35,9 +34,12 @@ export default function RootLayout({ return ( -
+

Loop Decoder

+ + + diff --git a/apps/web/src/app/tx/[chainID]/[hash]/form.tsx b/apps/web/src/app/tx/[chainID]/[hash]/form.tsx index 89d33f74..86859761 100644 --- a/apps/web/src/app/tx/[chainID]/[hash]/form.tsx +++ b/apps/web/src/app/tx/[chainID]/[hash]/form.tsx @@ -1,21 +1,17 @@ "use client"; import * as React from "react"; import { Label } from "@/components/ui/label"; -import { Textarea } from "@/components/ui/textarea"; import { DEFAULT_CHAIN_ID, transactions } from "../../../data"; import { useLocalStorage } from "usehooks-ts"; import { SidebarNav } from "@/components/ui/sidebar-nav"; -import { QuestionMarkCircledIcon } from "@radix-ui/react-icons"; -import { - HoverCard, - HoverCardTrigger, - HoverCardContent, -} from "@/components/ui/hover-card"; +import { PlayIcon } from "@radix-ui/react-icons"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; import { useRouter } from "next/navigation"; import { DecodedTx, Interpreter } from "@3loop/transaction-decoder"; import { interpretTx } from "@/lib/interpreter"; +import CodeBlock from "@/components/ui/code-block"; +import { NetworkSelect } from "@/components/ui/network-select"; export const sidebarNavItems = transactions.map((tx) => { return { @@ -40,7 +36,7 @@ export default function DecodingForm({ const [result, setResult] = React.useState(); const [schema, setSchema] = useLocalStorage( defaultInterpreter?.id ?? "unknown", - defaultInterpreter?.schema, + defaultInterpreter?.schema ); const router = useRouter(); @@ -51,7 +47,7 @@ export default function DecodingForm({ router.push(`/tx/${currentChainID}/${hash}`); }; - React.useEffect(() => { + const onRun = React.useCallback(() => { if (schema && defaultInterpreter != null && decoded != null) { const newInterpreter = { ...defaultInterpreter, @@ -66,88 +62,59 @@ export default function DecodingForm({ return (
-
-
-
-
-
-
- -
-
- - -
- -
+
+
+
+ -
- -