diff --git a/packages/dashboard/lib/DashboardServer.ts b/packages/dashboard/lib/DashboardServer.ts index 51f80c3e73b..86fd02eaddb 100644 --- a/packages/dashboard/lib/DashboardServer.ts +++ b/packages/dashboard/lib/DashboardServer.ts @@ -2,9 +2,9 @@ import express, { Application, NextFunction, Request, Response } from "express"; import path from "path"; import getPort from "get-port"; import open from "open"; +import { v4 as uuid } from "uuid"; import { fetchAndCompile } from "@truffle/fetch-and-compile"; import { sha1 } from "object-hash"; -import { v4 as uuid } from "uuid"; import Config from "@truffle/config"; import { dashboardProviderMessageType, diff --git a/packages/dashboard/package.json b/packages/dashboard/package.json index 8b8ec12f15d..0f4d62ff66b 100644 --- a/packages/dashboard/package.json +++ b/packages/dashboard/package.json @@ -33,31 +33,40 @@ "types": "dist/lib/index.d.ts", "dependencies": { "@emotion/react": "^11.9.3", - "@mantine/core": "^5.0.0", - "@mantine/hooks": "^5.0.0", - "@mantine/notifications": "^5.0.0", - "@mantine/prism": "^5.0.0", "@truffle/codec": "^0.17.0", "@truffle/config": "^1.3.58", + "@truffle/decoder": "^6.1.0", + "@truffle/fetch-and-compile": "^0.5.51", + "@truffle/codec-components": "^0.1.2", "@truffle/dashboard-message-bus": "^0.1.11", "@truffle/dashboard-message-bus-client": "^0.1.11", "@truffle/dashboard-message-bus-common": "^0.1.6", - "@truffle/decoder": "^6.1.0", - "@truffle/fetch-and-compile": "^0.5.51", + "@truffle/debugger": "^12.1.0", + "@truffle/debug-utils": "^6.0.54", + "@mantine/core": "^5.10.5", + "@mantine/hooks": "^5.10.5", + "@mantine/notifications": "^5.10.5", + "@mantine/prism": "^5.10.5", "cors": "^2.8.5", + "css-loader": "^6.8.1", "debug": "^4.3.2", "ethers": "^5.6.9", "express": "^4.17.1", + "ganache": "7.9.0", "get-port": "^5.1.1", + "highlightjs-solidity": "^2.0.6", "idb": "^7.1.1", - "isomorphic-ws": "^4.0.1", + "lowlight": "^2.8.1", "object-hash": "^3.0.0", "object-inspect": "^1.12.2", "open": "^8.4.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-feather": "^2.0.10", - "react-router-dom": "^6.3.0", + "react-router-dom": "^6.10.0", + "rehype-stringify": "^9.0.3", + "style-loader": "^3.3.3", + "unified": "^10.1.2", "uuid": "^9.0.0", "wagmi": "^0.6.3", "ws": "^7.2.0" diff --git a/packages/dashboard/src/App.tsx b/packages/dashboard/src/App.tsx index 5d22b19b81e..d3c136ce57e 100644 --- a/packages/dashboard/src/App.tsx +++ b/packages/dashboard/src/App.tsx @@ -9,6 +9,7 @@ import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom"; // Components import Layout from "src/components/composed/Layout"; import RPCs from "src/components/composed/RPCs"; +import Debugger from "src/components/composed/Debugger"; import Palette from "src/components/composed/Palette"; import MantineGlobal from "src/components/MantineGlobal"; @@ -26,6 +27,7 @@ function App(): JSX.Element { }> } /> } /> + } /> {process.env.NODE_ENV === "development" && ( } /> diff --git a/packages/dashboard/src/components/MantineGlobal.tsx b/packages/dashboard/src/components/MantineGlobal.tsx index d3964915234..6cba651f839 100644 --- a/packages/dashboard/src/components/MantineGlobal.tsx +++ b/packages/dashboard/src/components/MantineGlobal.tsx @@ -12,13 +12,22 @@ const fonts = [ } ]; -const truffleBgColorSelectors = [".mantine-AppShell-root"].join(", "); +const truffleBgColorSelectors = [".trfl-AppShell-root"].join(", "); const truffleOffBgColorSelectors = [ - ".mantine-Navbar-root", - ".mantine-Paper-root" + ".trfl-Navbar-root", + ".trfl-Paper-root" ].join(", "); +const watermelonHex = "#E86591"; +const orangeHex = "#E4A663"; +const orangeBrightHex = "#E78820"; +const redHex = "#D60000"; +const mintHex = "#3FE0C5"; +const mintDarkHex = "#0FBEA1"; +const greenHex = "#00D717"; +const greenDarkHex = "#00A412"; + function MantineGlobal(): JSX.Element { return ( svg": { @@ -50,25 +68,67 @@ function MantineGlobal(): JSX.Element { width: 28 } }, - ".mantine-Notification-root": { + ".trfl-Notification-root": { "backgroundColor": colorScheme === "dark" ? colors["dark"][4] : white, "padding": "1rem 1rem 1rem 2rem", - ".mantine-Notification-title": { + ".trfl-Notification-title": { fontSize: 15 } }, - ".mantine-Prism-code": { + ".trfl-Prism-code": { "backgroundColor": colorScheme === "dark" ? `${fn.darken(colors["truffle-brown"][9], 0.1)} !important` : `${colors["truffle-beige"][0]} !important`, - ".mantine-Prism-lineNumber": { + ".trfl-Prism-lineNumber": { color: colorScheme === "dark" ? colors["truffle-brown"][5] : colors["truffle-beige"][5] } + }, + ".codec-components-code-bracket": { + color: colors["truffle-beige"][8] + }, + ".hljs-comment, .hljs-quote": { + color: + theme.colorScheme === "dark" + ? colors["truffle-beige"][8] + : colors["truffle-beige"][6], + fontStyle: "italic" + }, + ".hljs-keyword": { + color: theme.colorScheme === "dark" ? mintHex : mintDarkHex, + fontWeight: "bold" + }, + ".hljs-subst": { + color: colors.pink[7], + fontWeight: "normal" + }, + ".hljs-number": { + color: redHex + }, + ".hljs-string, .hljs-doctag": { + color: theme.colorScheme === "dark" ? greenHex : greenDarkHex + }, + [[ + ".hljs-type", + ".hljs-class", + ".hljs-title", + ".hljs-section", + ".hljs-selector-id", + ".codec-components-code-name" + ].join(", ")]: { + color: theme.colorScheme === "dark" ? orangeHex : orangeBrightHex, + fontWeight: "bold" + }, + ".hljs-attribute": { + color: colors["truffle-teal"][8], + fontWeight: "normal" + }, + ".hljs-literal, .hljs-built_in, .hljs-builtin-name": { + color: watermelonHex } } ]; diff --git a/packages/dashboard/src/components/composed/Contracts.tsx b/packages/dashboard/src/components/composed/Contracts.tsx deleted file mode 100644 index 42a851739af..00000000000 --- a/packages/dashboard/src/components/composed/Contracts.tsx +++ /dev/null @@ -1,5 +0,0 @@ -function Contracts(): JSX.Element { - return
interact with contracts
; -} - -export default Contracts; diff --git a/packages/dashboard/src/components/composed/Debugger/Breakpoints/Breakpoint.tsx b/packages/dashboard/src/components/composed/Debugger/Breakpoints/Breakpoint.tsx new file mode 100644 index 00000000000..52c7b2d79ab --- /dev/null +++ b/packages/dashboard/src/components/composed/Debugger/Breakpoints/Breakpoint.tsx @@ -0,0 +1,68 @@ +import type { BreakpointType } from "src/components/composed/Debugger/utils"; +import { createStyles } from "@mantine/core"; + +const useStyles = createStyles(() => ({ + breakpointGroup: { + display: "flex", + marginBottom: 5 + }, + breakpointDelete: { + "&:hover": { + cursor: "pointer" + }, + "borderRadius": 8, + "backgroundColor": "#FA5252", + "width": 16, + "height": 16, + "marginRight": 16 + }, + breakpoint: { + "&:hover": { + cursor: "pointer" + } + } +})); + +type BreakpointProps = { + sourceName: string; + line: number; + sourceId: string; + handleBreakpointComponentClick: (arg: BreakpointType) => void; + handleBreakpointDeleteClick: (arg: BreakpointType) => void; +}; + +function Breakpoint({ + sourceName, + line, + sourceId, + handleBreakpointComponentClick, + handleBreakpointDeleteClick +}: BreakpointProps): JSX.Element | null { + const { classes } = useStyles(); + return ( +
+
+ handleBreakpointDeleteClick({ + sourceId, + line + }) + } + >
+
+ handleBreakpointComponentClick({ + sourceId, + line + }) + } + > + {sourceName} - line {line} +
+
+ ); +} + +export default Breakpoint; diff --git a/packages/dashboard/src/components/composed/Debugger/Breakpoints/index.tsx b/packages/dashboard/src/components/composed/Debugger/Breakpoints/index.tsx new file mode 100644 index 00000000000..243d5f8f5e3 --- /dev/null +++ b/packages/dashboard/src/components/composed/Debugger/Breakpoints/index.tsx @@ -0,0 +1,100 @@ +import { Source } from "src/components/composed/Debugger/utils"; +import { useDash } from "src/hooks"; +import * as path from "path"; +import { BreakpointType } from "src/components/composed/Debugger/utils"; +import Breakpoint from "src/components/composed/Debugger/Breakpoints/Breakpoint"; +import { Flex, createStyles } from "@mantine/core"; + +const useStyles = createStyles(theme => ({ + breakpointsContainer: { + overflow: "hidden", + height: "30%", + borderWidth: 1, + borderStyle: "solid", + borderRadius: 4, + marginBottom: 20, + borderColor: + theme.colorScheme === "dark" + ? theme.colors["truffle-brown"][5] + : `${theme.colors["truffle-beige"][5]}73` + }, + sectionHeader: { + height: 42, + fontSize: 16, + paddingTop: 10, + paddingLeft: 16, + backgroundColor: + theme.colorScheme === "dark" + ? `${theme.colors["truffle-beige"][8]}33` + : theme.colors["truffle-beige"][2], + borderBottom: "1px solid", + borderColor: + theme.colorScheme === "dark" + ? theme.colors["truffle-brown"][5] + : `${theme.colors["truffle-beige"][5]}73` + }, + breakpoints: { + overflow: "scroll", + height: "100%", + backgroundColor: + theme.colorScheme === "dark" ? theme.colors["truffle-brown"][8] : "white" + }, + breakpointsContent: { + paddingLeft: 10 + } +})); + +type BreakpointsArgs = { + sources: Source[]; + handleBreakpointComponentClick: (breakpoint: BreakpointType) => void; + handleBreakpointDeleteClick: (breakpoint: BreakpointType) => void; +}; + +function Breakpoints({ + sources, + handleBreakpointComponentClick, + handleBreakpointDeleteClick +}: BreakpointsArgs): JSX.Element | null { + const { classes } = useStyles(); + const { + state: { + debugger: { breakpoints } + } + } = useDash()!; + const breakpointsList: JSX.Element[] = []; + for (const source of sources) { + if ( + breakpoints[source.id] === undefined || + breakpoints[source.id].size === 0 + ) { + continue; + } + if (!source?.sourcePath) { + continue; + } + const iterator = breakpoints[source.id].values(); + const sourceName = path.basename(source.sourcePath); + for (const line of iterator) { + breakpointsList.push( + + ); + } + } + return ( + +
Breakpoints
+
+
{breakpointsList}
+
+
+ ); +} + +export default Breakpoints; diff --git a/packages/dashboard/src/components/composed/Debugger/Controls/ControlButton.tsx b/packages/dashboard/src/components/composed/Debugger/Controls/ControlButton.tsx new file mode 100644 index 00000000000..f0c98741d59 --- /dev/null +++ b/packages/dashboard/src/components/composed/Debugger/Controls/ControlButton.tsx @@ -0,0 +1,64 @@ +import { createElement } from "react"; +import type { Icon } from "react-feather"; +import { ActionIcon, createStyles, Tooltip } from "@mantine/core"; +import type { ActionIconProps } from "@mantine/core"; + +interface ControlButtonProps extends ActionIconProps { + icon: Icon; + step: () => Promise; + stepEffect: () => void; + stepping: boolean; + setStepping: React.Dispatch>; + tooltipLabel: string; +} + +function ControlButton({ + icon, + step, + stepEffect, + disabled, + stepping, + setStepping, + tooltipLabel, + ...props +}: ControlButtonProps): JSX.Element { + const useStyles = createStyles(theme => ({ + button: { + height: 42, + width: 42, + backgroundColor: + theme.colorScheme === "dark" + ? theme.colors["truffle-brown"][5] + : theme.colors["truffle-beige"][5] + } + })); + const { classes } = useStyles(); + + const actionIcon = ( + { + setStepping(true); + await step(); + setStepping(false); + stepEffect(); + }} + > + {createElement(icon)} + + ); + + // there is an issue with the tooltips where it doesn't vanish when the button + // switches to disabled state - we manually remove it to make it vanish + const output = disabled ? ( + actionIcon + ) : ( + {actionIcon} + ); + + return output; +} + +export default ControlButton; diff --git a/packages/dashboard/src/components/composed/Debugger/Controls/icons/Continue.tsx b/packages/dashboard/src/components/composed/Debugger/Controls/icons/Continue.tsx new file mode 100644 index 00000000000..6dbae3fdf4b --- /dev/null +++ b/packages/dashboard/src/components/composed/Debugger/Controls/icons/Continue.tsx @@ -0,0 +1,16 @@ +export const Continue = () => { + return ( + + + + ); +}; diff --git a/packages/dashboard/src/components/composed/Debugger/Controls/icons/Into.tsx b/packages/dashboard/src/components/composed/Debugger/Controls/icons/Into.tsx new file mode 100644 index 00000000000..1c77ba02bda --- /dev/null +++ b/packages/dashboard/src/components/composed/Debugger/Controls/icons/Into.tsx @@ -0,0 +1,18 @@ +export const Into = () => { + return ( + + + + ); +}; diff --git a/packages/dashboard/src/components/composed/Debugger/Controls/icons/Next.tsx b/packages/dashboard/src/components/composed/Debugger/Controls/icons/Next.tsx new file mode 100644 index 00000000000..d9a8d06f72b --- /dev/null +++ b/packages/dashboard/src/components/composed/Debugger/Controls/icons/Next.tsx @@ -0,0 +1,18 @@ +export const Next = () => { + return ( + + + + ); +}; diff --git a/packages/dashboard/src/components/composed/Debugger/Controls/icons/Out.tsx b/packages/dashboard/src/components/composed/Debugger/Controls/icons/Out.tsx new file mode 100644 index 00000000000..9556cb8119d --- /dev/null +++ b/packages/dashboard/src/components/composed/Debugger/Controls/icons/Out.tsx @@ -0,0 +1,18 @@ +export const Out = () => { + return ( + + + + ); +}; diff --git a/packages/dashboard/src/components/composed/Debugger/Controls/icons/Over.tsx b/packages/dashboard/src/components/composed/Debugger/Controls/icons/Over.tsx new file mode 100644 index 00000000000..b8edf3805f5 --- /dev/null +++ b/packages/dashboard/src/components/composed/Debugger/Controls/icons/Over.tsx @@ -0,0 +1,18 @@ +export const Over = () => { + return ( + + + + ); +}; diff --git a/packages/dashboard/src/components/composed/Debugger/Controls/icons/Reset.tsx b/packages/dashboard/src/components/composed/Debugger/Controls/icons/Reset.tsx new file mode 100644 index 00000000000..af246d6cfc0 --- /dev/null +++ b/packages/dashboard/src/components/composed/Debugger/Controls/icons/Reset.tsx @@ -0,0 +1,18 @@ +export const Reset = () => { + return ( + + + + ); +}; diff --git a/packages/dashboard/src/components/composed/Debugger/Controls/icons/index.tsx b/packages/dashboard/src/components/composed/Debugger/Controls/icons/index.tsx new file mode 100644 index 00000000000..4911af6bd4e --- /dev/null +++ b/packages/dashboard/src/components/composed/Debugger/Controls/icons/index.tsx @@ -0,0 +1,6 @@ +export * from "src/components/composed/Debugger/Controls/icons/Continue"; +export * from "src/components/composed/Debugger/Controls/icons/Into"; +export * from "src/components/composed/Debugger/Controls/icons/Next"; +export * from "src/components/composed/Debugger/Controls/icons/Out"; +export * from "src/components/composed/Debugger/Controls/icons/Over"; +export * from "src/components/composed/Debugger/Controls/icons/Reset"; diff --git a/packages/dashboard/src/components/composed/Debugger/Controls/index.tsx b/packages/dashboard/src/components/composed/Debugger/Controls/index.tsx new file mode 100644 index 00000000000..3b732d611f5 --- /dev/null +++ b/packages/dashboard/src/components/composed/Debugger/Controls/index.tsx @@ -0,0 +1,100 @@ +import { useState } from "react"; +import { useDash } from "src/hooks"; +import { + Continue, + Into, + Next, + Out, + Over, + Reset +} from "src/components/composed/Debugger/Controls/icons"; +import { Group } from "@mantine/core"; +import { selectors as $ } from "@truffle/debugger"; +import ControlButton from "src/components/composed/Debugger/Controls/ControlButton"; +import type { Session } from "src/components/composed/Debugger/utils"; + +interface ControlsProps { + session: Session | null; + stepEffect: () => void; +} + +function Controls({ session, stepEffect }: ControlsProps): JSX.Element { + const [stepping, setStepping] = useState(false); + const { + state: { + debugger: { breakpoints } + } + } = useDash()!; + const atStart = + session?.view($.trace.index) === 0 || + session?.view($.trace.index) === undefined; + const atEnd = session?.view($.trace.finished); + const disabled = atEnd || !session; + + const controlButtonProps = { + stepEffect, + stepping, + setStepping + }; + + const atLeastOneBreakpointSet = Object.values(breakpoints).some( + breakpointSet => { + return breakpointSet.size > 0; + } + ); + + return ( + + session.continueUntilBreakpoint()} + disabled={disabled || !atLeastOneBreakpointSet} + tooltipLabel="continue until breakpoint" + /> + session.stepNext()} + disabled={disabled} + tooltipLabel="step next" + /> + session.stepOver()} + disabled={disabled} + tooltipLabel="step over" + /> + session.stepInto()} + disabled={disabled} + tooltipLabel="step into" + /> + session.stepOut()} + disabled={disabled} + tooltipLabel="step out" + /> + session.reset()} + disabled={atStart} + tooltipLabel="reset" + /> + + ); +} + +export default Controls; diff --git a/packages/dashboard/src/components/composed/Debugger/ErrorNotification.tsx b/packages/dashboard/src/components/composed/Debugger/ErrorNotification.tsx new file mode 100644 index 00000000000..129a925785d --- /dev/null +++ b/packages/dashboard/src/components/composed/Debugger/ErrorNotification.tsx @@ -0,0 +1,72 @@ +import { Flex, Text, createStyles } from "@mantine/core"; +import { WarningIcon } from "src/components/composed/Debugger/WarningIcon"; + +const useStyles = createStyles(theme => ({ + notificationContainer: { + width: "100%", + height: "100%" + }, + notificationAndError: { + wordWrap: "break-word", + maxWidth: "55%", + backgroundColor: + theme.colorScheme === "dark" + ? "#082720" + : theme.colors["truffle-teal"][0], + border: `solid #C92A2A`, + borderRadius: 4 + }, + notificationHeader: { + backgroundColor: "#C92A2A", + width: "100%", + padding: 15, + display: "flex", + alignItems: "center" + }, + notificationHeaderText: { + color: "white" + }, + icon: { + marginRight: 10, + paddingTop: 5 + }, + errorTextContainer: { + padding: 15 + } +})); + +function ErrorNotification({ error }: { error: Error }): JSX.Element { + const { classes } = useStyles(); + + return ( + +
+
+
+ +
+
+ There's an issue... +
+
+
+ + An error occurred while initializing the debugger. Often errors can + be caused by being connected to the incorrect network for a specific + transaction. Ensure you have MetaMask connected to the appropriate + network for your transaction. See the error below for more + information. + + {error.message} +
+
+
+ ); +} + +export default ErrorNotification; diff --git a/packages/dashboard/src/components/composed/Debugger/Home/EtherscanApiKeyPrompt.tsx b/packages/dashboard/src/components/composed/Debugger/Home/EtherscanApiKeyPrompt.tsx new file mode 100644 index 00000000000..2de05edbe5c --- /dev/null +++ b/packages/dashboard/src/components/composed/Debugger/Home/EtherscanApiKeyPrompt.tsx @@ -0,0 +1,106 @@ +import { + Container, + Stack, + Flex, + Text, + Button, + Input, + createStyles +} from "@mantine/core"; +import { useInputState, useLocalStorage } from "@mantine/hooks"; +import { etherscanApiKeyName } from "src/utils/constants"; + +const useStyles = createStyles(theme => ({ + inputGroup: { + marginTop: 10, + input: { + height: 42, + borderTopRightRadius: 0, + borderBottomRightRadius: 0, + border: 0 + } + }, + reset: { + "&:hover": { + cursor: "pointer" + }, + "color": "#E03131", + "fontSize": 14 + }, + promptContainer: { + width: "50%", + borderRadius: 4, + backgroundColor: + theme.colorScheme === "dark" + ? "#082720" + : theme.colors["truffle-teal"][0], + border: `solid ${theme.colors["truffle-teal"][7]}`, + padding: 15 + } +})); + +function EtherScanApiKeyPrompt() { + const { classes } = useStyles(); + + const [etherscanApiKey, setEtherscanApiKey, removeEtherscanApiKey] = + useLocalStorage({ + key: etherscanApiKeyName + }); + const [inputValue, setInputValue] = useInputState(""); + + const buttonStyles = { + height: "42px", + borderTopLeftRadius: "0px", + borderBottomLeftRadius: "0px" + }; + + if (etherscanApiKey === undefined) { + const onButtonClick = () => { + setEtherscanApiKey(inputValue); + }; + + return ( + + + ️🏎 Want a speedier experience? Add your EtherScan API key to + more quickly download external contract sources. + + + + + + + ); + } else { + const onElementClick = () => removeEtherscanApiKey(); + return ( + + + + 🕵️‍♀️ We found an EtherScan API key that you've previously entered via + your browser. + {" "} + Delete it from browser storage to use another key. + + + Delete API Key + + + ); + } +} + +export default EtherScanApiKeyPrompt; diff --git a/packages/dashboard/src/components/composed/Debugger/Home/index.tsx b/packages/dashboard/src/components/composed/Debugger/Home/index.tsx new file mode 100644 index 00000000000..8b168cb6306 --- /dev/null +++ b/packages/dashboard/src/components/composed/Debugger/Home/index.tsx @@ -0,0 +1,42 @@ +import { Stack, Text, createStyles } from "@mantine/core"; +import EtherscanApiKeyPrompt from "src/components/composed/Debugger/Home/EtherscanApiKeyPrompt"; + +const useStyles = createStyles(theme => ({ + welcomeMessage: { + textAlign: "left", + width: "100%", + height: "80%", + color: + theme.colorScheme === "dark" + ? theme.colors["truffle-beige"][2] + : theme.colors["truffle-beige"][9] + }, + halfWidth: { + width: "50%" + } +})); + +function Home(): JSX.Element { + const { classes } = useStyles(); + + return ( + + + Welcome! + + + Here you can paste a transaction hash to begin debugging it. You can + also start the debugger by clicking the Debug button on a signature + request. + + + + ); +} + +export default Home; diff --git a/packages/dashboard/src/components/composed/Debugger/PreparingSession.tsx b/packages/dashboard/src/components/composed/Debugger/PreparingSession.tsx new file mode 100644 index 00000000000..691dda141cb --- /dev/null +++ b/packages/dashboard/src/components/composed/Debugger/PreparingSession.tsx @@ -0,0 +1,53 @@ +import { useState, useEffect } from "react"; +import { createStyles, Flex, Loader } from "@mantine/core"; + +const useStyles = createStyles(() => ({ + title: { + fontSize: 18, + marginLeft: 20 + }, + container: { + height: "40%" + } +})); + +function PreparingSession({ + ganacheLoggingOutput +}: { + ganacheLoggingOutput: string; +}): JSX.Element { + const { classes } = useStyles(); + const [ganacheOutput, setGanacheOutput] = useState([]); + + useEffect(() => { + if (ganacheOutput.length === 6) { + setGanacheOutput(ganacheOutput.slice(1).concat(ganacheLoggingOutput)); + } else { + setGanacheOutput(ganacheOutput.concat(ganacheLoggingOutput)); + } + /* eslint-disable-next-line react-hooks/exhaustive-deps */ + }, [ganacheLoggingOutput]); + + return ( + +
+ + +
+ Preparing your debugger session, this may take some time. +
+
+ {ganacheLoggingOutput.length > 0 && ( +
{ganacheOutput.join("\n")}
+ )} +
+
+ ); +} + +export default PreparingSession; diff --git a/packages/dashboard/src/components/composed/Debugger/Sources/Source/LineNumber.tsx b/packages/dashboard/src/components/composed/Debugger/Sources/Source/LineNumber.tsx new file mode 100644 index 00000000000..eb95f25fead --- /dev/null +++ b/packages/dashboard/src/components/composed/Debugger/Sources/Source/LineNumber.tsx @@ -0,0 +1,92 @@ +import { highlightedTextClass } from "src/components/composed/Debugger/utils"; +import { createStyles } from "@mantine/core"; +import { useDash } from "src/hooks"; + +interface LineNumberProps { + lineHasHighlighting: boolean; + lineNumber: number; + lineNumberGutterWidth: number; + sourceId: string; +} + +const useStyles = createStyles(() => ({ + lineNumber: { + "&:hover": { + cursor: "pointer" + }, + "display": "flex" + }, + spacer: { + minHeight: 22, + minWidth: 22, + width: 22, + marginRight: 5 + }, + breakpointSpacerContainer: { + minHeight: 22, + minWidth: 22, + width: 22, + display: "flex", + marginRight: 5, + alignItems: "center" + }, + breakpointSpacer: { + width: 16, + height: 16, + backgroundColor: "#FA5252", + borderRadius: 25 + } +})); + +function LineNumber({ + lineHasHighlighting, + lineNumberGutterWidth, + sourceId, + lineNumber +}: LineNumberProps): JSX.Element { + const { classes } = useStyles(); + const { + state: { + debugger: { breakpoints } + }, + operations: { toggleDebuggerBreakpoint } + } = useDash()!; + // if the line contains highlighting we highlight the line number as well + const lineNumberDisplay = lineHasHighlighting ? ( + + {" ".repeat(lineNumberGutterWidth - lineNumber.toString().length)} + {lineNumber} + {" "} + + ) : ( + + {" ".repeat(lineNumberGutterWidth - lineNumber.toString().length)} + {lineNumber} + {" "} + + ); + + const spacer = + breakpoints && + breakpoints[sourceId] && + breakpoints[sourceId].has(lineNumber) ? ( +
+
+
+ ) : ( +
+ ); + + const handleClick = () => { + toggleDebuggerBreakpoint({ line: lineNumber, sourceId }); + }; + + return ( +
+ {spacer} + {lineNumberDisplay} +
+ ); +} + +export default LineNumber; diff --git a/packages/dashboard/src/components/composed/Debugger/Sources/Source/SourceLine.tsx b/packages/dashboard/src/components/composed/Debugger/Sources/Source/SourceLine.tsx new file mode 100644 index 00000000000..38285c5a718 --- /dev/null +++ b/packages/dashboard/src/components/composed/Debugger/Sources/Source/SourceLine.tsx @@ -0,0 +1,45 @@ +import { createStyles } from "@mantine/core"; + +const useStyles = createStyles(() => ({ + sourceLine: { + height: 22, + display: "flex" + }, + content: { + height: 22, + display: "flex" + } +})); + +interface SourceLineProps { + line: string; + lineNumber: number; + lastLine: boolean; + firstHighlightedLine: boolean; + sourceId: string; +} + +function SourceLine({ + line, + lineNumber, + lastLine, + sourceId +}: SourceLineProps): JSX.Element { + const { classes } = useStyles(); + + if (!lastLine) line += "\n"; + + const lineId = `${sourceId.slice(-10)}-${lineNumber}`; + + return ( +
+
+
+ ); +} + +export default SourceLine; diff --git a/packages/dashboard/src/components/composed/Debugger/Sources/Source/index.tsx b/packages/dashboard/src/components/composed/Debugger/Sources/Source/index.tsx new file mode 100644 index 00000000000..007b85b7a06 --- /dev/null +++ b/packages/dashboard/src/components/composed/Debugger/Sources/Source/index.tsx @@ -0,0 +1,100 @@ +import SourceLine from "src/components/composed/Debugger/Sources/Source/SourceLine"; +import LineNumber from "src/components/composed/Debugger/Sources/Source/LineNumber"; +import type { + Source as SourceType, + SourceRange +} from "src/components/composed/Debugger/utils"; +import { convertSourceToHtml } from "src/components/composed/Debugger/utils"; +import { createStyles, Flex } from "@mantine/core"; + +const useStyles = createStyles(theme => ({ + sourceContainer: { + overflow: "scroll", + height: "100%", + backgroundColor: + theme.colorScheme === "dark" ? theme.colors["truffle-brown"][8] : "white" + }, + source: { + paddingTop: 15, + paddingLeft: 15 + }, + lineNumbersContainer: { + height: 22, + display: "flex", + flexDirection: "column" + }, + sourceLineContainer: { + height: 22, + display: "flex", + flexDirection: "column" + } +})); + +interface SourceProps { + source: SourceType; + sourceRange: SourceRange; +} + +function Source({ source, sourceRange }: SourceProps): JSX.Element { + const { classes } = useStyles(); + const rawSourceLines = convertSourceToHtml({ source, sourceRange }); + + const { start, end } = sourceRange; + const lineNumberGutterWidth = rawSourceLines.length.toString().length; + + const lineNumbers: JSX.Element[] = []; + const sourceLines: JSX.Element[] = []; + + rawSourceLines.forEach((line: string, index: number) => { + const sourceLineKey = `${source.id}-sourceLine-${index}`; + const lineNumberKey = `${source.id}-lineNumber-${index}`; + const lineHasHighlighting = + source.id === sourceRange.source.id && + index >= start.line && + (end.line === null || + end.column === null || + (end.column === 0 && index < end.line) || + (end.column > 0 && index <= end.line)); + const firstHighlightedLine = lineHasHighlighting && index === start.line; + + const lineNumber = index + 1; + + const sourceLineProps = { + key: sourceLineKey, + line, + lineNumber, + lineNumberGutterWidth, + lastLine: index === rawSourceLines.length - 1, + firstHighlightedLine, + sourceId: source.id, + lineHasHighlighting + }; + + const lineNumberProps = { + key: lineNumberKey, + lineHasHighlighting, + lineNumber, + lineNumberGutterWidth, + sourceId: source.id + }; + + sourceLines.push(); + lineNumbers.push(); + }); + + return ( +
+
+        
+          
{lineNumbers}
+
{sourceLines}
+
+
+
+ ); +} + +export default Source; diff --git a/packages/dashboard/src/components/composed/Debugger/Sources/UnknownSource.tsx b/packages/dashboard/src/components/composed/Debugger/Sources/UnknownSource.tsx new file mode 100644 index 00000000000..802b0c7f4fb --- /dev/null +++ b/packages/dashboard/src/components/composed/Debugger/Sources/UnknownSource.tsx @@ -0,0 +1,31 @@ +import { createStyles } from "@mantine/core"; + +const useStyles = createStyles(theme => ({ + unknownSourceContainer: { + height: "100%", + padding: 15, + backgroundColor: + theme.colorScheme === "dark" + ? theme.colors["truffle-beige"][9] + : "#FFF3BF" + }, + unknownSourceTitle: { + fontSize: 18 + } +})); + +function UnknownSource({ address }: { address: string }): JSX.Element { + const { classes } = useStyles(); + return ( +
+
Unknown Source
+
+ We're unable to locate the source material for the contract at the + following address: {address}. Please consider recompiling with Truffle + Dashboard running if you have the sources locally. +
+
+ ); +} + +export default UnknownSource; diff --git a/packages/dashboard/src/components/composed/Debugger/Sources/index.tsx b/packages/dashboard/src/components/composed/Debugger/Sources/index.tsx new file mode 100644 index 00000000000..9d1238d8c78 --- /dev/null +++ b/packages/dashboard/src/components/composed/Debugger/Sources/index.tsx @@ -0,0 +1,172 @@ +import { useEffect, useRef } from "react"; +import { basename } from "path"; +import { createStyles, Tabs } from "@mantine/core"; +import Source from "src/components/composed/Debugger/Sources/Source"; +import UnknownSource from "src/components/composed/Debugger/Sources/UnknownSource"; +import type { + SourceRange, + Session, + Source as SourceType, + UnknownAddress +} from "src/components/composed/Debugger/utils"; + +const useStyles = createStyles((theme, _params, _getRef) => ({ + maxHeight: { + height: "100%" + }, + sourceContent: { + height: "98%", + overflow: "hidden", + borderStyle: "solid", + borderRadius: "0px 0px 4px 4px", + borderWidth: 1, + borderColor: + theme.colorScheme === "dark" + ? theme.colors["truffle-brown"][3] + : `${theme.colors["truffle-beige"][5]}73` + }, + tab: { + fontSize: 16, + backgroundColor: + theme.colorScheme === "dark" + ? theme.colors["truffle-brown"][5] + : theme.colors["truffle-beige"][3], + borderStyle: "solid", + height: 42, + borderTopWidth: 1, + borderLeftWidth: 1, + borderRightWidth: 1, + borderBottomWidth: 0, + borderColor: + theme.colorScheme === "dark" + ? theme.colors["truffle-brown"][5] + : `${theme.colors["truffle-beige"][5]}73`, + color: theme.colors["truffle-beige"][7] + }, + activeTab: { + fontSize: 16, + backgroundColor: + theme.colorScheme === "dark" + ? theme.colors["truffle-brown"][8] + : theme.colors["truffle-beige"][4], + color: theme.colorScheme === "dark" ? "white" : "black", + borderStyle: "solid", + height: 42, + borderTopWidth: 1, + borderLeftWidth: 1, + borderRightWidth: 1, + borderBottomWidth: 0, + borderColor: + theme.colorScheme === "dark" + ? theme.colors["truffle-brown"][5] + : `${theme.colors["truffle-beige"][5]}73` + } +})); + +interface SourcesProps { + session: Session; + sessionUpdated: any; + sources: SourceType[]; + currentSourceRange: SourceRange; + unknownAddresses: UnknownAddress[] | null; + currentSourceId: string | undefined; + setCurrentSourceId: (sourceId: string) => void; +} + +function Sources({ + sources, + session, + sessionUpdated, + currentSourceRange, + unknownAddresses, + currentSourceId, + setCurrentSourceId +}: SourcesProps): JSX.Element { + const { classes } = useStyles(); + const currentSourceIdRef = useRef(currentSourceId); + currentSourceIdRef.current = currentSourceId; + + // display the first source when currentSourceRange is `undefined` + useEffect(() => { + if (currentSourceId === undefined) { + setCurrentSourceId(sources[0].id); + } + }, [sources, currentSourceId, setCurrentSourceId]); + + useEffect(() => { + const sessionSourceId = currentSourceRange.source.id; + if (sessionSourceId !== currentSourceIdRef.current) { + setCurrentSourceId(sessionSourceId); + } + }, [ + session, + sessionUpdated, + currentSourceRange.source.id, + setCurrentSourceId + ]); + + const unknownSourcesExist = unknownAddresses && unknownAddresses.length > 0; + + let sourcesContent, unknownSourcesContent; + if (currentSourceId !== undefined) { + sourcesContent = sources.map((source: SourceType) => ( + + + + )); + unknownSourcesContent = !unknownSourcesExist + ? [] + : unknownAddresses!.map((address: string) => ( + + + + )); + } + + return ( + + + {sources.map((source: SourceType) => { + const tabClass = + currentSourceId === source.id ? classes.activeTab : classes.tab; + return ( + + {basename(source.sourcePath)} + + ); + })} + {!unknownSourcesExist + ? null + : unknownAddresses!.map((address: string) => { + const tabClass = (currentSourceId = + currentSourceId === address ? classes.activeTab : classes.tab); + return ( + + Unknown Contract + + ); + })} + +
+ {sourcesContent} + {unknownSourcesContent} +
+
+ ); +} + +export default Sources; diff --git a/packages/dashboard/src/components/composed/Debugger/Stack.tsx b/packages/dashboard/src/components/composed/Debugger/Stack.tsx new file mode 100644 index 00000000000..e08823092aa --- /dev/null +++ b/packages/dashboard/src/components/composed/Debugger/Stack.tsx @@ -0,0 +1,97 @@ +import { useEffect, useState } from "react"; +import type { Session } from "src/components/composed/Debugger/utils"; +import { createStyles, Flex } from "@mantine/core"; + +const useStyles = createStyles(theme => ({ + sectionHeader: { + height: 42, + fontSize: 16, + paddingTop: 10, + paddingLeft: 16, + backgroundColor: + theme.colorScheme === "dark" + ? `${theme.colors["truffle-beige"][8]}33` + : theme.colors["truffle-beige"][2], + borderBottom: "1px solid", + borderColor: + theme.colorScheme === "dark" + ? theme.colors["truffle-brown"][5] + : `${theme.colors["truffle-beige"][5]}73` + }, + stackContainer: { + overflow: "hidden", + height: "30%", + borderWidth: 1, + borderStyle: "solid", + borderRadius: 4, + borderColor: + theme.colorScheme === "dark" + ? theme.colors["truffle-brown"][5] + : `${theme.colors["truffle-beige"][5]}73` + }, + stack: { + overflow: "scroll", + height: "100%", + backgroundColor: + theme.colorScheme === "dark" ? theme.colors["truffle-brown"][8] : "white" + }, + stackContent: { + paddingLeft: 10 + } +})); + +type StackArgs = { + session: Session; + currentStep: string; +}; + +function Stack({ session, currentStep }: StackArgs): JSX.Element | null { + const { classes } = useStyles(); + const [stackReport, setStackReport] = useState(null); + // when the debugger step changes, update variables + useEffect(() => { + async function getStack() { + const report = session.view(session.selectors.stacktrace.current.report); + if (!report) return; + // we need to display this information in the reverse order + report.reverse(); + setStackReport(report); + } + getStack(); + }, [currentStep, session]); + + const output = stackReport + ? stackReport.map((reportItem: any, index: number) => { + const { address, contractName, functionName, isConstructor, type } = + reportItem; + let name: string; + if (contractName && functionName) { + name = `${contractName}.${functionName}`; + } else if (contractName) { + name = + type === "external" && isConstructor + ? `new ${contractName}` + : contractName; + } else if (functionName) { + name = functionName; + } else { + name = "unknown function"; + } + const displayAddress = + address === undefined ? "unknown address" : address; + const stackDisplay = `at ${name} [address ${displayAddress}]`; + return
{stackDisplay}
; + }) + : null; + + return ( + +
Stack
+
+
{output}
+
+
+ ); +} + +export default Stack; diff --git a/packages/dashboard/src/components/composed/Debugger/Variables.tsx b/packages/dashboard/src/components/composed/Debugger/Variables.tsx new file mode 100644 index 00000000000..7b7141064f1 --- /dev/null +++ b/packages/dashboard/src/components/composed/Debugger/Variables.tsx @@ -0,0 +1,151 @@ +import { useEffect, useState } from "react"; +import type { Session } from "src/components/composed/Debugger/utils"; +import * as CodecComponents from "@truffle/codec-components/react"; +import "@truffle/codec-components/react-styles"; +import { createStyles, Flex } from "@mantine/core"; + +const useStyles = createStyles(theme => ({ + sectionHeader: { + height: 42, + fontSize: 16, + paddingTop: 10, + paddingLeft: 16, + backgroundColor: + theme.colorScheme === "dark" + ? `${theme.colors["truffle-beige"][8]}33` + : theme.colors["truffle-beige"][2], + borderBottom: "1px solid", + borderColor: + theme.colorScheme === "dark" + ? theme.colors["truffle-brown"][5] + : `${theme.colors["truffle-beige"][5]}73` + }, + variablesContainer: { + overflow: "hidden", + height: "40%", + borderWidth: 1, + borderStyle: "solid", + borderRadius: 4, + marginBottom: 20, + borderColor: + theme.colorScheme === "dark" + ? theme.colors["truffle-brown"][5] + : `${theme.colors["truffle-beige"][5]}73` + }, + variables: { + overflow: "scroll", + height: "100%", + backgroundColor: + theme.colorScheme === "dark" ? theme.colors["truffle-brown"][8] : "white" + }, + variablesContent: { + paddingLeft: 10 + }, + variablesSection: { + listStyleType: "none", + marginBlockStart: "0em", + marginBlockEnd: "0em", + marginInlineStart: "0em", + marginInlineEnd: "0em", + paddingInlineStart: "0em", + marginBottom: "1em" + }, + variablesTypes: { + fontSize: 12, + fontWeight: 800, + textDecoration: "underline", + marginBottom: "0.5em" + } +})); + +type VariablesArgs = { + session: Session; + currentStep: string; +}; + +function Variables({ + session, + currentStep +}: VariablesArgs): JSX.Element | null { + const { classes } = useStyles(); + const [variables, setVariables] = useState(null); + + // when the debugger step changes, update variables + useEffect(() => { + async function getVariables() { + const sections = session.view( + session.selectors.data.current.identifiers.sections + ); + const vars = await session.variables(); + if (!vars || Object.keys(vars).length === 0) return; + + const variableValues: { [key: string]: any } = {}; + + // section here is a variable category/type such as a Solidity built-in + // or contract variable + Object.keys(sections).forEach((section: string) => { + const sectionVars = sections[section]; + if (!sectionVars || sectionVars.length === 0) return; + sectionVars.forEach((varName: string) => { + variableValues[section] = { + ...variableValues[section], + [varName]: vars[varName] + }; + }); + }); + setVariables(variableValues); + } + + getVariables(); + }, [currentStep, session, classes.variablesTypes, classes.variablesSection]); + + const output = variables + ? Object.keys(variables).map(sectionName => { + // if there are no variables for a section/type, display just the name + if ( + !variables[sectionName] || + Object.keys(variables[sectionName]).length === 0 + ) { + return ( +
+
{sectionName}
+
    +
    + ); + } + + // calculate variable values and put them in the appropriate section + const variableVals = Object.keys(variables[sectionName]).map( + (variableName: string) => { + return ( +
  • + +
  • + ); + } + ); + return ( +
    +
    {sectionName}
    +
      {variableVals}
    +
    + ); + }) + : null; + + return ( + +
    Variables
    +
    +
    {output}
    +
    +
    + ); +} + +export default Variables; diff --git a/packages/dashboard/src/components/composed/Debugger/WarningIcon.tsx b/packages/dashboard/src/components/composed/Debugger/WarningIcon.tsx new file mode 100644 index 00000000000..2befba7530b --- /dev/null +++ b/packages/dashboard/src/components/composed/Debugger/WarningIcon.tsx @@ -0,0 +1,33 @@ +export const WarningIcon = () => { + return ( + + + + + + ); +}; diff --git a/packages/dashboard/src/components/composed/Debugger/index.tsx b/packages/dashboard/src/components/composed/Debugger/index.tsx new file mode 100644 index 00000000000..182ddfb2bc8 --- /dev/null +++ b/packages/dashboard/src/components/composed/Debugger/index.tsx @@ -0,0 +1,326 @@ +import { useState, useEffect } from "react"; +import { Input, Button, Header, Grid, createStyles } from "@mantine/core"; +import { useInputState, useCounter, useLocalStorage } from "@mantine/hooks"; +import Controls from "src/components/composed/Debugger/Controls"; +import Sources from "src/components/composed/Debugger/Sources"; +import Variables from "src/components/composed/Debugger/Variables"; +import Breakpoints from "src/components/composed/Debugger/Breakpoints"; +import Stack from "src/components/composed/Debugger/Stack"; +import PreparingSession from "src/components/composed/Debugger/PreparingSession"; +import Home from "src/components/composed/Debugger/Home"; +import ErrorNotification from "src/components/composed/Debugger/ErrorNotification"; +import { + initDebugger, + forkNetworkWithTxAndInitDebugger, + SessionStatus +} from "src/components/composed/Debugger/utils"; +import { useDash } from "src/hooks"; +import { getCurrentSourceRange } from "src/components/composed/Debugger/utils"; +import type { + BreakpointType, + SourceRange +} from "src/components/composed/Debugger/utils"; +import { etherscanApiKeyName } from "src/utils/constants"; + +const useStyles = createStyles(theme => ({ + debugger: { + height: "100vh", + overflowY: "hidden" + }, + inputGroup: { + paddingTop: 26, + paddingLeft: 32, + paddingRight: 32, + display: "flex", + border: "none", + backgroundColor: + theme.colorScheme === "dark" + ? theme.colors["truffle-brown"][8] + : theme.colors["truffle-beige"][3] + }, + inputAndButton: { + "display": "flex", + "flexGrow": 1, + "& > div > input": { + borderTopRightRadius: 0, + borderBottomRightRadius: 0, + border: 0, + height: 42 + } + }, + button: { + height: "42px", + borderTopLeftRadius: "0px", + borderBottomLeftRadius: "0px" + }, + mainContent: { + height: "calc(100vh - 108px)", + paddingBottom: 36, + margin: 32, + fontSize: 12, + fontWeight: 700 + }, + fullHeight: { + height: "100%" + } +})); + +function Debugger(): JSX.Element { + const { classes } = useStyles(); + const [inputValue, setInputValue] = useInputState(""); + const [sessionUpdated, { increment: sessionTick }] = useCounter(); + const { + operations, + state: { + debugger: { sources, unknownAddresses, session, txToRun } + } + } = useDash()!; + + const [etherscanApiKey] = useLocalStorage({ key: etherscanApiKeyName }); + const [error, setError] = useState(); + const [loggingOutput, setLoggingOutput] = useState(""); + const [status, setStatus] = useState(SessionStatus.Inactive); + + if (txToRun && status === SessionStatus.Inactive) { + setStatus(SessionStatus.Initializing); + forkNetworkWithTxAndInitDebugger({ + tx: txToRun, + operations, + setStatus, + etherscanApiKey, + setLoggingOutput + }); + } + + // goToBreakpoint stores breakpoint info when a user clicks on one + // so we can jump to it in Sources + const [goToBreakpoint, setGoToBreakpoint] = useState( + null + ); + const inputsDisabled = + status === SessionStatus.PreparingForInitialization || + status === SessionStatus.Initializing || + status === SessionStatus.Fetching || + status === SessionStatus.Starting; + const formDisabled = !/0x[a-z0-9]{64}/i.test(inputValue) || inputsDisabled; + + // currentSourceId is the "active" source displayed in Sources + const [currentSourceId, setCurrentSourceId] = useState( + undefined + ); + + let currentSourceRange: SourceRange | Partial = { + traceIndex: -1 + }; + + let currentStep; + // wait until the debugger has been initialized and then get source info + if (session) { + currentSourceRange = getCurrentSourceRange(session); + // if the starting source is unknown, we may get `undefined` in the source + // range - in that case we'll initialize it manually from the stacktrace + if (!currentSourceRange.source?.id && !currentSourceId) { + const currentContractAddress = session.view( + session.selectors.stacktrace.current.report + )[0].address; + // when the contract is "unknown", the source id will be the address + // we need this if check so that no loop occurs when the value is falsy + if (currentContractAddress) { + setCurrentSourceId(currentContractAddress); + } + } + currentStep = session.view(session.selectors.trace.index); + } + + const scrollToLine = ({ + sourceId, + line + }: { + sourceId: string; + line: number; + }) => { + const lineNumber = line + 1; + const lineId = `${sourceId.slice(-10)}-${lineNumber}`; + const targetLine: HTMLElement | null = document.getElementById(lineId); + if (targetLine) { + const offsetTop = targetLine.offsetTop; + const scroller = document.getElementById( + `source-${sourceId.slice(-10)}` + )!; + const scrollerHeight = scroller.offsetHeight; + // approx. 60% gets the line to the middle of the container + scroller.scrollTop = offsetTop - scrollerHeight * 0.6; + } + }; + + const startDebugger = async () => { + setStatus(SessionStatus.PreparingForInitialization); + const provider = window.ethereum as any; + const networkId = await provider.request({ + method: "net_version", + params: [] + }); + const ganacheOptions = { + fork: { provider }, + // normally Ganache forks 5 blocks behind the current block, turn this off + preLatestConfirmations: 0, + logging: { + logger: { + log: (message: string) => { + setLoggingOutput(message); + } + } + } + }; + const fetchingOptions = { + networkId, + etherscanApiKey + }; + const txHash = inputValue === "" ? undefined : inputValue; + try { + await initDebugger({ + ganacheOptions, + operations, + setStatus, + provider, + fetchingOptions, + txHash + }); + } catch (error) { + setError(error as Error); + } + }; + + const onButtonClick = () => startDebugger(); + + // make input responsive to "enter" key + const handleKeyDown = (e: any) => { + if (formDisabled) return; + if (e.keyCode === 13) { + startDebugger(); + } + }; + + useEffect(() => { + if (isSourceRange(currentSourceRange) && currentSourceRange.source.id) { + const { source, start } = currentSourceRange!; + scrollToLine({ sourceId: source.id, line: start.line }); + } + }, [currentStep, currentSourceId]); + + // check whether we need to scroll to a breakpoint + // this is to ensure the source has fully rendered before scrolling + useEffect(() => { + if (goToBreakpoint !== null) { + const { sourceId, line } = goToBreakpoint; + scrollToLine({ sourceId, line }); + setGoToBreakpoint(null); + } + }, [goToBreakpoint]); + + const handleBreakpointComponentClick = ({ + sourceId, + line + }: BreakpointType) => { + setCurrentSourceId(sourceId); + setGoToBreakpoint({ sourceId, line }); + }; + + const handleBreakpointDeleteClick = ({ sourceId, line }: BreakpointType) => { + operations.toggleDebuggerBreakpoint({ + sourceId, + line + }); + }; + + const isSourceRange = (item: any): item is SourceRange => { + // when source exists, that means it should be a full SourceRange + return item.source !== undefined; + }; + + let content; + if (session && sources && isSourceRange(currentSourceRange)) { + content = ( +
    + + + + + + + + + + +
    + ); + } + + const preparingSession = + status === SessionStatus.PreparingForInitialization || + status === SessionStatus.Initializing || + status === SessionStatus.Fetching || + status === SessionStatus.Starting; + let mainBody; + // we check the session in case the user navigated elsewhere in dashboard and + // then comes back to the debugger - their session will still be loaded + if (status === SessionStatus.Inactive && !session) { + mainBody = ; + } else if (preparingSession) { + mainBody = ; + } else { + mainBody = content; + } + + if (error) { + return ; + } else { + return ( +
    +
    + +
    + + +
    +
    + {mainBody} +
    + ); + } +} + +export default Debugger; diff --git a/packages/dashboard/src/components/composed/Debugger/utils/constants.ts b/packages/dashboard/src/components/composed/Debugger/utils/constants.ts new file mode 100644 index 00000000000..86eb324cd1e --- /dev/null +++ b/packages/dashboard/src/components/composed/Debugger/utils/constants.ts @@ -0,0 +1 @@ +export const highlightedTextClass = "truffle-debugger-text-highlighted"; diff --git a/packages/dashboard/src/components/composed/Debugger/utils/index.ts b/packages/dashboard/src/components/composed/Debugger/utils/index.ts new file mode 100644 index 00000000000..8850daeb2a1 --- /dev/null +++ b/packages/dashboard/src/components/composed/Debugger/utils/index.ts @@ -0,0 +1,5 @@ +export * from "src/components/composed/Debugger/utils/session"; +export * from "src/components/composed/Debugger/utils/source"; +export * from "src/components/composed/Debugger/utils/status"; +export * from "src/components/composed/Debugger/utils/types"; +export * from "src/components/composed/Debugger/utils/constants"; diff --git a/packages/dashboard/src/components/composed/Debugger/utils/session.ts b/packages/dashboard/src/components/composed/Debugger/utils/session.ts new file mode 100644 index 00000000000..d7a6365aff0 --- /dev/null +++ b/packages/dashboard/src/components/composed/Debugger/utils/session.ts @@ -0,0 +1,263 @@ +import { forTx } from "@truffle/debugger"; +import { provider as ganacheProvider } from "ganache"; +import * as Codec from "@truffle/codec"; +import type { Session, Source } from "src/components/composed/Debugger/utils"; +import { SessionStatus } from "src/components/composed/Debugger/utils"; +import type { Compilation } from "@truffle/compile-common"; +import { getTransactionSourcesBeforeStarting } from "@truffle/debug-utils"; + +export async function forkNetworkWithTxAndInitDebugger({ + tx, + operations, + setStatus, + etherscanApiKey, + setLoggingOutput +}: any) { + const { method, params } = tx.message.payload; + const ganacheOptions = { + fork: { + provider: window.ethereum as any + }, + wallet: { + unlockedAccounts: [params[0].from] + }, + logging: { + logger: { + log: (message: string) => { + setLoggingOutput(message); + } + } + } + }; + + const forkedProvider = ganacheProvider(ganacheOptions); + const networkId = await forkedProvider.request({ + method: "net_version", + params: [] + }); + + const result = await forkedProvider.request({ method, params }); + return initDebugger({ + ganacheOptions: { + ...ganacheOptions, + fork: { + provider: forkedProvider + } + }, + fetchingOptions: { + etherscanApiKey, + networkId + }, + operations, + setStatus, + txHash: result + }); +} + +export async function initDebugger({ + ganacheOptions, + operations, + setStatus, + txHash, + fetchingOptions +}: any) { + const compilations = await operations.getCompilations(); + + const { session, sources, unknownAddresses } = await setupSession({ + handleCompilations: operations.handleCompilations, + ganacheOptions, + fetchingOptions, + txHash, + compilations, + callbacks: { + onInit: () => setStatus(SessionStatus.Initializing), + onFetch: () => setStatus(SessionStatus.Fetching), + onStart: () => setStatus(SessionStatus.Starting), + onReady: () => setStatus(SessionStatus.Ready) + } + }); + operations.setDebuggerSessionData({ sources, unknownAddresses, session }); +} + +type FetchingOptions = { + etherscanApiKey?: string; + networkId: string; +}; + +type SetupSessionArgs = { + txHash: string; + compilations: Compilation[]; + ganacheOptions: any; + callbacks?: { + onInit?: () => void; + onFetch?: () => void; + onStart?: () => void; + onReady?: () => void; + }; + handleCompilations: ( + compilations: Compilation[], + hashes?: string[] + ) => Promise; + fetchingOptions: FetchingOptions; +}; + +export async function setupSession({ + txHash, + ganacheOptions, + compilations, + callbacks, + fetchingOptions, + handleCompilations +}: SetupSessionArgs): Promise<{ + session: Session; + sources: Source[]; + unknownAddresses: string[]; +}> { + callbacks?.onInit?.(); + const { session, sources, unrecognizedAddresses } = await createSession({ + txHash, + ganacheOptions, + compilations, + fetchingOptions, + handleCompilations + }); + + callbacks?.onFetch?.(); + callbacks?.onStart?.(); + await session.startFullMode(); + + callbacks?.onReady?.(); + + return { session, sources, unknownAddresses: unrecognizedAddresses }; +} + +type CreateSessionArgs = { + txHash: string; + compilations: Compilation[]; + ganacheOptions: any; + fetchingOptions: FetchingOptions; + handleCompilations: ( + compilations: Compilation[], + hashes?: string[] + ) => Promise; +}; + +async function createSession({ + txHash, + compilations, + ganacheOptions, + fetchingOptions, + handleCompilations +}: CreateSessionArgs): Promise<{ + session: Session; + sources: Source[]; + unrecognizedAddresses: string[]; +}> { + let session; + try { + session = await forTx(txHash, { + provider: ganacheProvider(ganacheOptions), + compilations: Codec.Compilations.Utils.shimCompilations(compilations), + lightMode: true + }); + } catch (error) { + // @ts-ignore + if (!error.message.includes("Unknown transaction")) { + throw error; + } + throw new Error( + `The transaction hash isn't recognized on the network you are connected` + + ` to. Please ensure you are on the appropriate network for ` + + `transaction hash ${txHash}.` + ); + } + + const $ = session.selectors; + const affectedInstances: { [address: string]: any } = session.view( + $.session.info.affectedInstances + ); + + let unrecognizedAddresses: string[] = []; + for (const [address, value] of Object.entries(affectedInstances)) { + if (value.contractName === undefined && value.binary !== "0x") { + unrecognizedAddresses.push(address); + } + } + if (unrecognizedAddresses.length > 0) { + const { networkId, etherscanApiKey } = fetchingOptions; + await fetchCompilationsAndAddToSession({ + session, + networkId, + addresses: unrecognizedAddresses, + etherscanApiKey, + handleCompilations + }); + } + const sources = await getTransactionSourcesBeforeStarting(session); + // we need to transform these into the format dashboard uses + const transformedSources = Object.values(sources).flatMap( + ({ id, sourcePath, source: contents, language }: any) => + language === "Solidity" ? [{ id, sourcePath, contents, language }] : [] + ); + return { + sources: transformedSources, + session, + unrecognizedAddresses + }; +} + +type FetchCompilationsAndAddToSessionArgs = { + session: Session; + networkId: string; + addresses: string[]; + etherscanApiKey?: string; + handleCompilations: ( + compilations: Compilation[], + hashes?: string[] + ) => Promise; +}; + +async function fetchCompilationsAndAddToSession({ + session, + networkId, + addresses, + etherscanApiKey, + handleCompilations +}: FetchCompilationsAndAddToSessionArgs): Promise<{ + unknownAddresses: string[]; +}> { + const host = window.location.hostname; + const port = + process.env.NODE_ENV === "development" ? 24012 : window.location.port; + const fetchAndCompileEndpoint = `http://${host}:${port}/fetch-and-compile`; + + let unknownAddresses = []; + for (const address of addresses) { + let url = `${fetchAndCompileEndpoint}?address=${address}&networkId=${networkId}`; + if (etherscanApiKey) { + url = url + `ðerscanApiKey=${etherscanApiKey}`; + } + + const fetchResult = await fetch(url); + if (!fetchResult.ok) { + throw new Error( + `There was an error fetching the source material for ${address}. ` + + `Check the Truffle Dashboard server logging for more information.` + ); + } + const { compilations, hashes } = await fetchResult.json(); + if (compilations.length === 0) { + unknownAddresses.push(address); + continue; + } + + await handleCompilations(compilations, hashes); + const shimmedCompilations = Codec.Compilations.Utils.shimCompilations( + compilations, + `externalFor(${address})Via(Etherscan)` + ); + + await session.addExternalCompilations(shimmedCompilations); + } + return { unknownAddresses }; +} diff --git a/packages/dashboard/src/components/composed/Debugger/utils/source/getCurrentSourceRange.ts b/packages/dashboard/src/components/composed/Debugger/utils/source/getCurrentSourceRange.ts new file mode 100644 index 00000000000..9a63f5c096e --- /dev/null +++ b/packages/dashboard/src/components/composed/Debugger/utils/source/getCurrentSourceRange.ts @@ -0,0 +1,16 @@ +import type { Session } from "src/components/composed/Debugger/utils"; + +export function getCurrentSourceRange(session: Session) { + const { selectors: $ } = session; + const traceIndex = session.view($.trace.index); + const { id } = session.view($.sourcemapping.current.source); + const { + lines: { start, end } + } = session.view($.sourcemapping.current.sourceRange); + return { + traceIndex, + source: { id }, + start, + end + }; +} diff --git a/packages/dashboard/src/components/composed/Debugger/utils/source/htmlUtils.ts b/packages/dashboard/src/components/composed/Debugger/utils/source/htmlUtils.ts new file mode 100644 index 00000000000..f4c80d0ca7a --- /dev/null +++ b/packages/dashboard/src/components/composed/Debugger/utils/source/htmlUtils.ts @@ -0,0 +1,47 @@ +export function removeCompleteSpanPairs(spanTags: string[] | null) { + if (spanTags === null) return []; + let stillNeedsProcessing: boolean = true; + while (stillNeedsProcessing) { + let spanPairRemovedThisPass: boolean = false; + for (let index = 0; index < spanTags.length - 1; index++) { + if ( + spanTags[index].startsWith("" + ) { + spanTags.splice(index, 2); //remove the pair + spanPairRemovedThisPass = true; + break; + } + } + // when we don't remove anything else it means we've gotten all pairs + if (!spanPairRemovedThisPass) { + stillNeedsProcessing = false; + } + } + return spanTags; +} + +export function completeMultilineSpans(sourceLines: string[]) { + let openTags: string[] = []; + return sourceLines.reduce((a, line) => { + let processedLine: string = ""; + let numberOfOpenTags: number = openTags.length; + const spanTags = line.match(/(]+>|<\/span>)/g); + + const incompleteSpanTags = removeCompleteSpanPairs(spanTags); + // start the line with all open tags + processedLine = openTags.join(""); + for (const incompleteTag of incompleteSpanTags) { + if (incompleteTag === "") { + openTags.pop(); + numberOfOpenTags = numberOfOpenTags - 1; + } else { + openTags.push(incompleteTag); + numberOfOpenTags = numberOfOpenTags + 1; + } + } + processedLine = processedLine + line + "".repeat(numberOfOpenTags); + a.push(processedLine); + return a; + }, [] as string[]); +} diff --git a/packages/dashboard/src/components/composed/Debugger/utils/source/index.ts b/packages/dashboard/src/components/composed/Debugger/utils/source/index.ts new file mode 100644 index 00000000000..d45677d0c0c --- /dev/null +++ b/packages/dashboard/src/components/composed/Debugger/utils/source/index.ts @@ -0,0 +1,3 @@ +export * from "src/components/composed/Debugger/utils/source/getCurrentSourceRange"; +export * from "src/components/composed/Debugger/utils/source/htmlUtils"; +export * from "src/components/composed/Debugger/utils/source/source"; diff --git a/packages/dashboard/src/components/composed/Debugger/utils/source/source.ts b/packages/dashboard/src/components/composed/Debugger/utils/source/source.ts new file mode 100644 index 00000000000..5763f32967d --- /dev/null +++ b/packages/dashboard/src/components/composed/Debugger/utils/source/source.ts @@ -0,0 +1,197 @@ +import { unified } from "unified"; +import rehypeStringify from "rehype-stringify"; +import { lowlight } from "lowlight/lib/core"; +import { solidity } from "highlightjs-solidity"; +import { highlightedTextClass } from "src/components/composed/Debugger/utils"; +import { completeMultilineSpans } from "src/components/composed/Debugger/utils/source/htmlUtils"; +import type { + Source, + SourceRange +} from "src/components/composed/Debugger/utils"; + +export function convertSourceToHtml({ + source, + sourceRange +}: { + source: Source; + sourceRange: SourceRange; +}) { + // DETERMINE WHERE TEXT IS HIGHLIGHTED AND MARK + // for lines that are fully highlighted, we don't update them but mark it in + // an array - for partially hightlighted lines, we add comment markers for + // where spans will go later designating debugger highlighting - we mark + // them here as comments so lowlight doesn't choke on html + const { sourceWithHighlightedMarkings, fullyHighlightedLines } = + markTextHighlighted(source, sourceRange); + + // ADD SYNTAX HIGHLIGHTING (HTML) AND BREAK INTO INDIVIDUAL LINES + const sourceWithSyntaxHighlighting = addSyntaxHighlighting( + sourceWithHighlightedMarkings + ).split("\n"); + + // COMPLETE LOWLIGHT'S HTML SINCE WE BROKE THE SOURCE INTO LINES + // HACK: we need to detect where lowlight added spans for multiline stuff + // and add more because we break the source into lines + const sourceWithAddedSpans = completeMultilineSpans( + sourceWithSyntaxHighlighting + ); + + // REPLACE OUR HIGHLIGHTING MARKERS WITH HTML + // replace comment markers with spans denoting the debugger's highlighted text + return replaceTextHighlightedMarkings({ + source: sourceWithAddedSpans, + fullyHighlightedLines + }); +} + +const textHighlightingBeginsMarker = ` /****truffle-debugger-highlight-begin****/`; +const textHighlightingEndsMarker = ` /****truffle-debugger-highlight-end****/`; +const closingSpan = ``; +// lowlight wraps our markers in spans which we need to remove when we replace +// the markers with our spans for highlighting +const highlightJsCommentSpan = ``; + +export function markTextHighlighted(source: Source, sourceRange: SourceRange) { + const fullyHighlightedLines = new Set(); + const editedLines = source.contents.split("\n").map((line, index) => { + if (line.length === 0) { + return line; + } + const { start, end } = sourceRange; + // HACK: at times the debugger returns values of `null` which can mean that + // the source maps are bad - one case is where the end values are too large + // for a given source - here we try to account for this by assuming it + // goes until the end of the file + if ( + source.id === sourceRange.source.id && + index >= start.line && + end.line === null + ) { + fullyHighlightedLines.add(index); + return line; + } + // END HACK + + const lineHasHighlighting = + source.id === sourceRange.source.id && + index >= start.line && + index <= end.line!; + + if (!lineHasHighlighting) return line; + + const wholeLineHighlighted = + (lineHasHighlighting && + // the line is in the middle of a block highlighted section + start.line < index && + index < end.line!) || + // just the current line is highlighted + (index === start.line && + start.column === 0 && + end.column === line.length - 1) || + // this line is the start of block highlighting + (index === start.line && start.column === 0 && index < end.line!) || + // this line is the last line of block highlighting + (index === end.line! && + index > start.line && + end.column === line.length - 1); + // if the whole line is highlighted, we'll add the highlighting later - + // weird html issues occur when we add it here due to us breaking up the + // html into lines, thus breaking multiline tags + if (wholeLineHighlighted) { + fullyHighlightedLines.add(index); + return line; + } + + let editedLine; + // highlighting contained within a single line + if (start.line === index && end.line === index) { + const segments = [ + line.slice(0, start.column), + line.slice(start.column, end.column!), + line.slice(end.column!) + ]; + editedLine = + segments[0] + + textHighlightingBeginsMarker + + segments[1] + + textHighlightingEndsMarker + + segments[2]; + } + // highlighting starting on the current line but ending on another + if (start.line === index && end.line! > index) { + const segments = [line.slice(0, start.column), line.slice(start.column)]; + editedLine = + segments[0] + + textHighlightingBeginsMarker + + segments[1] + + textHighlightingEndsMarker; + } + // highlighting started on a previous line but ending on the current one + if (start.line < index && end.line === index) { + const segments = [line.slice(0, end.column!), line.slice(end.column!)]; + editedLine = + textHighlightingBeginsMarker + + segments[0] + + textHighlightingEndsMarker + + segments[1]; + } + return editedLine; + }); + + return { + fullyHighlightedLines, + sourceWithHighlightedMarkings: { + ...source, + contents: editedLines.join("\n") + } + }; +} + +lowlight.registerLanguage("solidity", solidity); +const processor = unified().use(rehypeStringify); + +export function addSyntaxHighlighting(source: Source) { + const highlighted = lowlight.highlight("solidity", source.contents); + return processor.stringify(highlighted); +} + +export function replaceTextHighlightedMarkings({ + source, + fullyHighlightedLines +}: { + source: string[]; + fullyHighlightedLines: Set; +}) { + return source.map((line, index) => { + const highlightedTextTag = ``; + const closingTag = ``; + // wrap the entire thing if it is fully highlighted + if (fullyHighlightedLines.has(index)) { + return highlightedTextTag + line + ""; + } + // we need to add the space to make lowlight parse the comment correctly + // as a comment as there are some cases where it marks it incorrectly + return ( + line + .replaceAll( + " " + + highlightJsCommentSpan + + textHighlightingBeginsMarker.slice(1) + + closingSpan, + highlightedTextTag + ) + .replaceAll( + " " + + highlightJsCommentSpan + + textHighlightingEndsMarker.slice(1) + + closingSpan, + closingTag + ) + // sometimes the markings don't get wrapped by lowlight for some reason + // we replace the ones it missed here + .replaceAll(textHighlightingBeginsMarker.slice(1), highlightedTextTag) + .replaceAll(textHighlightingEndsMarker.slice(1), closingTag) + .replace(/(?; + + startFullMode(): Promise; + + view(selector: Selector): any; + + variables(options?: { indicateUnknown?: boolean }): Promise; + + continueUntilBreakpoint(): Promise; + stepNext(): Promise; + stepOver(): Promise; + stepInto(): Promise; + stepOut(): Promise; + reset(): Promise; + addBreakpoint(arg: { line: number; sourceId: string }): void; + removeBreakpoint(arg: { line: number; sourceId: string }): void; +} diff --git a/packages/dashboard/src/components/composed/Layout.tsx b/packages/dashboard/src/components/composed/Layout.tsx index b79c211a684..4a56ea2c570 100644 --- a/packages/dashboard/src/components/composed/Layout.tsx +++ b/packages/dashboard/src/components/composed/Layout.tsx @@ -16,10 +16,14 @@ import { const ARBITRARY_ANALYTICS_NEXT_ASK_THRESHOLD_IN_DAYS = 365; -const useStyles = createStyles((_theme, _params, _getRef) => ({ +const useStyles = createStyles((theme, _params, _getRef) => ({ main: { maxHeight: "100vh", - overflow: "auto" + overflow: "auto", + backgroundColor: + theme.colorScheme === "dark" + ? theme.colors["truffle-brown"][8] + : theme.colors["truffle-beige"][3] } })); diff --git a/packages/dashboard/src/components/composed/Notice/content/ConfirmChain.tsx b/packages/dashboard/src/components/composed/Notice/content/ConfirmChain.tsx index 2108707f392..af2283bf447 100644 --- a/packages/dashboard/src/components/composed/Notice/content/ConfirmChain.tsx +++ b/packages/dashboard/src/components/composed/Notice/content/ConfirmChain.tsx @@ -18,7 +18,7 @@ const useStyles = createStyles((_theme, _params, _getRef) => ({ justifyContent: "center" }, label: { - "> .mantine-Title-root": { + "> .trfl-Title-root": { display: "flex", alignItems: "center", justifyContent: "center" diff --git a/packages/dashboard/src/components/composed/RPCs/RPC/Overview.tsx b/packages/dashboard/src/components/composed/RPCs/RPC/Overview.tsx index bfdbd58321f..f67bf7f5592 100644 --- a/packages/dashboard/src/components/composed/RPCs/RPC/Overview.tsx +++ b/packages/dashboard/src/components/composed/RPCs/RPC/Overview.tsx @@ -5,6 +5,7 @@ import { useDash } from "src/hooks"; import { inspectDecoding } from "src/utils/dash"; import type { Decoding } from "src/utils/dash"; import ChainIcon from "src/components/common/ChainIcon"; +import { useNavigate } from "react-router-dom"; const useStyles = createStyles((theme, _params, getRef) => { const { @@ -48,6 +49,12 @@ const useStyles = createStyles((theme, _params, getRef) => { "&:hover": { backgroundColor: colors["truffle-teal"][9] } + }, + [`& .${getRef("debugButton")}`]: { + "backgroundColor": fn.rgba(colors["truffle-teal"][7], 0.6), + "&:hover": { + backgroundColor: colors["truffle-teal"][7] + } } }, info: { @@ -74,7 +81,7 @@ const useStyles = createStyles((theme, _params, getRef) => { ? colors["truffle-beige"][3] : colors["truffle-beige"][8] }, - buttons: { + buttonsContainer: { minWidth: buttonsWidth }, button: { @@ -85,6 +92,7 @@ const useStyles = createStyles((theme, _params, getRef) => { }, rejectButton: { ref: getRef("rejectButton") }, confirmButton: { ref: getRef("confirmButton") }, + debugButton: { ref: getRef("debugButton") }, confirmButtonRightIcon: { marginLeft: 4, marginRight: 6 @@ -106,6 +114,8 @@ type OverviewProps = { onRejectButtonLeave: React.MouseEventHandler; onConfirmButtonEnter: React.MouseEventHandler; onConfirmButtonLeave: React.MouseEventHandler; + onDebugButtonEnter: React.MouseEventHandler; + onDebugButtonLeave: React.MouseEventHandler; }; function Overview({ @@ -121,25 +131,31 @@ function Overview({ onRejectButtonEnter, onRejectButtonLeave, onConfirmButtonEnter, - onConfirmButtonLeave + onConfirmButtonLeave, + onDebugButtonEnter, + onDebugButtonLeave }: OverviewProps): JSX.Element { const { method } = lifecycle.message.payload; const decodingInspected = inspectDecoding(decoding); const { state: { chainInfo }, - operations: { userConfirmMessage, userRejectMessage } + operations: { userConfirmMessage, userRejectMessage, setTxToRun } } = useDash()!; const { classes } = useStyles(); + const navigate = useNavigate(); const onConfirmButtonClick = () => void userConfirmMessage(lifecycle); const onRejectButtonClick = () => void userRejectMessage(lifecycle); + const onDebugButtonClick = () => { + setTxToRun(lifecycle); + navigate("/debugger"); + }; return ( - )} - - + + + + + - + ); } diff --git a/packages/dashboard/src/components/composed/RPCs/RPC/index.tsx b/packages/dashboard/src/components/composed/RPCs/RPC/index.tsx index cfc7a5447a5..718dffbdfbb 100644 --- a/packages/dashboard/src/components/composed/RPCs/RPC/index.tsx +++ b/packages/dashboard/src/components/composed/RPCs/RPC/index.tsx @@ -45,6 +45,7 @@ function RPC({ lifecycle }: RPCProps): JSX.Element { useDisclosure(false); const [confirmButtonHovered, confirmButtonHoveredHandlers] = useDisclosure(false); + const [debugButtonHovered, debugButtonHoveredHandlers] = useDisclosure(false); const [collapsedDetailsHovered, collapsedDetailsHoveredHandlers] = useDisclosure(false); const { classes } = useStyles(); @@ -94,7 +95,8 @@ function RPC({ lifecycle }: RPCProps): JSX.Element { clicked || overviewBackHovered || rejectButtonHovered || - confirmButtonHovered + confirmButtonHovered || + debugButtonHovered } onBackClick={clickedHandlers.toggle} onBackEnter={overviewBackHoveredHandlers.open} @@ -103,6 +105,8 @@ function RPC({ lifecycle }: RPCProps): JSX.Element { onRejectButtonLeave={rejectButtonHoveredHandlers.close} onConfirmButtonEnter={confirmButtonHoveredHandlers.open} onConfirmButtonLeave={confirmButtonHoveredHandlers.close} + onDebugButtonEnter={debugButtonHoveredHandlers.open} + onDebugButtonLeave={debugButtonHoveredHandlers.close} />
    { const { colors, colorScheme, radius, fn } = theme; @@ -8,14 +9,6 @@ const useStyles = createStyles((theme, _params, _getRef) => { display: "block", borderRadius: radius.sm }, - enabled: { - "&:hover": { - backgroundColor: - colorScheme === "dark" - ? fn.darken(colors["truffle-brown"][8], 0.08) - : fn.darken(colors["truffle-beige"][4], 0.08) - } - }, disabled: { cursor: "default", pointerEvents: "none", @@ -23,6 +16,20 @@ const useStyles = createStyles((theme, _params, _getRef) => { colorScheme === "dark" ? colors["truffle-brown"][4] : colors["truffle-beige"][5] + }, + active: { + backgroundColor: + colorScheme === "dark" + ? colors["truffle-brown"][6] + : colors["truffle-beige"][2] + }, + inactive: { + "&:hover": { + backgroundColor: + colorScheme === "dark" + ? fn.lighten(colors["truffle-brown"][8], 0.08) + : fn.darken(colors["truffle-beige"][4], 0.08) + } } }; }); @@ -43,14 +50,15 @@ function NavButton({ disabled }: NavButtonProps): JSX.Element { const { classes } = useStyles(); + const location = useLocation(); return ( diff --git a/packages/dashboard/src/components/composed/Sidebar/Middle/index.tsx b/packages/dashboard/src/components/composed/Sidebar/Middle/index.tsx index 436b781fd5b..1bcd7c2baf5 100644 --- a/packages/dashboard/src/components/composed/Sidebar/Middle/index.tsx +++ b/packages/dashboard/src/components/composed/Sidebar/Middle/index.tsx @@ -1,5 +1,5 @@ -import { Navbar, Badge, Indicator } from "@mantine/core"; -import { Zap, Archive, Aperture } from "react-feather"; +import { Navbar, Indicator } from "@mantine/core"; +import { Zap, Crosshair, Aperture } from "react-feather"; import NavButton from "src/components/composed/Sidebar/Middle/NavButton"; import { useDash } from "src/hooks"; @@ -10,16 +10,6 @@ function Middle(): JSX.Element { const numRequests = providerMessages.size; const featherIconProps = { size: 18 }; - const comingSoonBadge = ( - - coming soon - - ); - return ( } - badge={comingSoonBadge} - disabled={true} + label="Debugger" + to="/debugger" + icon={} /> {process.env.NODE_ENV === "development" && ( ({ sideBar: { - minWidth: 370, - maxWidth: 370, + height: "100vh", + width: 370, borderRight: "none" } })); diff --git a/packages/dashboard/src/contexts/DashContext/DashContext.ts b/packages/dashboard/src/contexts/DashContext/DashContext.ts index 81fa9fe3fb5..1d31ae2db07 100644 --- a/packages/dashboard/src/contexts/DashContext/DashContext.ts +++ b/packages/dashboard/src/contexts/DashContext/DashContext.ts @@ -2,6 +2,11 @@ import { createContext } from "react"; import type { ReceivedMessageLifecycle } from "@truffle/dashboard-message-bus-client"; import type { DashboardProviderMessage } from "@truffle/dashboard-message-bus-common"; import type { State, Action } from "src/contexts/DashContext"; +import type { + SetDebuggerSessionDataArgs, + ToggleDebuggerBreakpointArgs +} from "src/contexts/DashContext/types"; +import type { Compilation } from "@truffle/compile-common"; export interface ContextValue { state: State; @@ -14,6 +19,16 @@ export interface ContextValue { ) => any; toggleNotice: () => void; updateAnalyticsConfig: (value: boolean) => void; + setDebuggerSessionData: (value: SetDebuggerSessionDataArgs) => void; + getCompilations: (compilations: Compilation[]) => Promise; + handleCompilations: ( + compilations: Compilation[], + hashes?: string[] + ) => Promise; + toggleDebuggerBreakpoint: (value: ToggleDebuggerBreakpointArgs) => void; + setTxToRun: ( + lifecycle: ReceivedMessageLifecycle + ) => void; }; dispatch?: React.Dispatch; } diff --git a/packages/dashboard/src/contexts/DashContext/DashProvider.tsx b/packages/dashboard/src/contexts/DashContext/DashProvider.tsx index 6acb6872f98..c5f38409cfb 100644 --- a/packages/dashboard/src/contexts/DashContext/DashProvider.tsx +++ b/packages/dashboard/src/contexts/DashContext/DashProvider.tsx @@ -1,6 +1,7 @@ import { useReducer, useEffect, useRef, useMemo, useCallback } from "react"; import { useAccount, useNetwork } from "wagmi"; import { sha1 } from "object-hash"; +import { deleteDB } from "idb/with-async-ittr"; import type { ReceivedMessageLifecycle } from "@truffle/dashboard-message-bus-client"; import { isCliEventMessage, @@ -24,6 +25,10 @@ import { rejectMessage, getChainNameByID } from "src/utils/dash"; +import type { + SetDebuggerSessionDataArgs, + ToggleDebuggerBreakpointArgs +} from "src/contexts/DashContext/types"; const ARBITRARY_DB_MAX_BYTES = 500_000_000; const ARBITRARY_DB_MAX_PERCENT = 0.8; @@ -82,10 +87,8 @@ function DashProvider({ children }: DashProviderProps): JSX.Element { [] ); - const handleWorkflowCompileResult = useCallback( - async (data: WorkflowCompileResult) => { - const { compilations } = data; - + const handleCompilations = useCallback( + async (compilations: Compilation[], hashes?: string[]) => { if (compilations.length === 0 || !stateRef.current.decoder) return; let decoderNeedsUpdate = false; @@ -100,7 +103,9 @@ function DashProvider({ children }: DashProviderProps): JSX.Element { // - Batch inserting. // - Mirroring some kind of db state. for (const compilation of compilations) { - const hash = sha1(compilation); + let hash = hashes + ? hashes[compilations.indexOf(compilation)] + : sha1(compilation); // If the in-memory decoder doesn't have this compilation, // save it for decoder re-init after this for-loop ends. @@ -169,8 +174,9 @@ function DashProvider({ children }: DashProviderProps): JSX.Element { // Handle compilations separately because: // a) Db operations are async // b) Avoid duplicate work (e.g. loop, hash) - handleWorkflowCompileResult( + handleCompilations( (message as CliEventMessage).payload.data + .compilations ); } } else if (isLogMessage(message)) { @@ -229,14 +235,22 @@ function DashProvider({ children }: DashProviderProps): JSX.Element { dispatch({ type: "set-analytics-config", data }); }; + const cleanGanacheDb = async () => { + const ganacheDb = await indexedDB.databases(); + for (const { name } of ganacheDb) { + if (name?.startsWith("/tmp/ganache_")) await deleteDB(name); + } + }; + const init = async () => { await initDecoder(); await initBusClient(); await initAnalytics(); + await cleanGanacheDb(); }; init(); - }, [state, handleWorkflowCompileResult]); + }, [state, handleCompilations]); useEffect(() => { const updateChainInfo = () => { @@ -296,6 +310,46 @@ function DashProvider({ children }: DashProviderProps): JSX.Element { body: JSON.stringify({ value }) }); // No need to update state afterwards + }, + handleCompilations, + getCompilations: async (): Promise => { + const { dbPromise } = state; + const compilations = await (await dbPromise).getAll("Compilation"); + return compilations.map(entry => entry.data); + }, + setDebuggerSessionData: ({ + unknownAddresses, + sources, + session + }: SetDebuggerSessionDataArgs) => { + dispatch({ + type: "set-debugger-session-data", + data: { + sources, + unknownAddresses, + session + } + }); + }, + setTxToRun: ( + lifecycle: ReceivedMessageLifecycle + ) => { + dispatch({ + type: "set-tx-to-run", + data: lifecycle + }); + }, + toggleDebuggerBreakpoint: ({ + line, + sourceId + }: ToggleDebuggerBreakpointArgs) => { + dispatch({ + type: "toggle-debugger-breakpoint", + data: { + line, + sourceId + } + }); } }; diff --git a/packages/dashboard/src/contexts/DashContext/state.ts b/packages/dashboard/src/contexts/DashContext/state.ts index 3d772adcc74..461cad92150 100644 --- a/packages/dashboard/src/contexts/DashContext/state.ts +++ b/packages/dashboard/src/contexts/DashContext/state.ts @@ -36,6 +36,13 @@ export const initialState: State = { }); } }), + debugger: { + sources: null, + unknownAddresses: null, + session: null, + txToRun: undefined, + breakpoints: {} + }, decoder: null, decoderCompilations: null, decoderCompilationHashes: null, @@ -66,6 +73,49 @@ export const reducer = (state: State, action: Action): State => { return { ...state, notice: { ...state.notice, ...data } }; case "set-analytics-config": return { ...state, analyticsConfig: data }; + case "toggle-debugger-breakpoint": + const { line, sourceId } = data; + // the front-end starts line numbering at 1 while the debugger + // starts them at 0 + const debuggerLine = line - 1; + const breakpointExists = state.debugger.breakpoints![sourceId].has(line); + const newBreakpointStateForSource = new Set( + state.debugger.breakpoints![sourceId] + ); + if (breakpointExists) { + state.debugger.session!.removeBreakpoint({ + line: debuggerLine, + sourceId + }); + newBreakpointStateForSource.delete(line); + } else { + state.debugger.session!.addBreakpoint({ line: debuggerLine, sourceId }); + newBreakpointStateForSource.add(line); + } + return { + ...state, + debugger: { + ...state.debugger, + breakpoints: { + ...state.debugger.breakpoints, + [sourceId]: newBreakpointStateForSource + } + } + }; + case "set-debugger-session-data": + const breakpointsInitialState: { [sourceId: string]: Set } = {}; + if (data.sources !== null) { + for (const source of data.sources) { + breakpointsInitialState[source.id] = new Set(); + } + } + return { + ...state, + debugger: { + ...data, + breakpoints: breakpointsInitialState + } + }; case "handle-message": // Copy state, // modify it depending on message type, @@ -100,6 +150,14 @@ export const reducer = (state: State, action: Action): State => { } return newState; + case "set-tx-to-run": + return { + ...state, + debugger: { + ...state.debugger, + txToRun: data + } + }; case "update-provider-message-sender": const newProviderMessages = new Map(state.providerMessages); const newSender = data; diff --git a/packages/dashboard/src/contexts/DashContext/types/Action.ts b/packages/dashboard/src/contexts/DashContext/types/Action.ts index a7bbc1c7583..9fbef86d282 100644 --- a/packages/dashboard/src/contexts/DashContext/types/Action.ts +++ b/packages/dashboard/src/contexts/DashContext/types/Action.ts @@ -1,14 +1,36 @@ import type { ReceivedMessageLifecycle } from "@truffle/dashboard-message-bus-client"; -import type { Message } from "@truffle/dashboard-message-bus-common"; +import type { + Message, + DashboardProviderMessage +} from "@truffle/dashboard-message-bus-common"; import type { State } from "src/contexts/DashContext/types"; +import type { + Source, + Session, + UnknownAddress +} from "src/components/composed/Debugger/utils"; export type ActionType = | "set-decoder" | "set-chain-info" | "set-notice" | "set-analytics-config" + | "set-tx-to-run" | "handle-message" - | "update-provider-message-sender"; + | "update-provider-message-sender" + | "set-debugger-session-data" + | "toggle-debugger-breakpoint"; + +export type SetDebuggerSessionDataArgs = { + sources: Source[] | null; + unknownAddresses: UnknownAddress[] | null; + session: Session; +}; + +export type ToggleDebuggerBreakpointArgs = { + line: number; + sourceId: string; +}; export interface BaseAction { type: ActionType; @@ -22,6 +44,11 @@ export interface SetDecoderAction extends BaseAction { >; } +export interface SetDebuggerSessionDataAction extends BaseAction { + type: "set-debugger-session-data"; + data: SetDebuggerSessionDataArgs; +} + export interface SetChainInfoAction extends BaseAction { type: "set-chain-info"; data: State["chainInfo"]; @@ -42,15 +69,31 @@ export interface HandleMessageAction extends BaseAction { data: ReceivedMessageLifecycle; } +export interface SetTxToRunAction extends BaseAction { + type: "set-tx-to-run"; + data: ReceivedMessageLifecycle; +} + export interface UpdateProviderMessageSenderAction extends BaseAction { type: "update-provider-message-sender"; data: string; } +export interface ToggleDebuggerBreakpointAction extends BaseAction { + type: "toggle-debugger-breakpoint"; + data: { + line: number; + sourceId: string; + }; +} + export type Action = | SetDecoderAction | SetChainInfoAction | SetNoticeAction | SetAnalyticsConfigAction | HandleMessageAction - | UpdateProviderMessageSenderAction; + | UpdateProviderMessageSenderAction + | SetDebuggerSessionDataAction + | SetTxToRunAction + | ToggleDebuggerBreakpointAction; diff --git a/packages/dashboard/src/contexts/DashContext/types/State.ts b/packages/dashboard/src/contexts/DashContext/types/State.ts index f95edcf5857..fb3cb8c06f5 100644 --- a/packages/dashboard/src/contexts/DashContext/types/State.ts +++ b/packages/dashboard/src/contexts/DashContext/types/State.ts @@ -7,10 +7,26 @@ import type { import type { DashboardProviderMessage } from "@truffle/dashboard-message-bus-common"; import type { Schema } from "src/contexts/DashContext"; import type { NoticeContent } from "src/components/composed/Notice/content/types"; +import type { + Source, + Session, + UnknownAddress +} from "src/components/composed/Debugger/utils"; + +type BreakpointState = { + [sourceId: string]: Set; +}; export interface State { busClient: DashboardMessageBusClient; dbPromise: Promise>; + debugger: { + sources: Source[] | null; + unknownAddresses: UnknownAddress[] | null; + session: Session | null; + breakpoints: BreakpointState; + txToRun?: ReceivedMessageLifecycle; + }; decoder: ProjectDecoder | null; decoderCompilations: Array | null; decoderCompilationHashes: Set< diff --git a/packages/dashboard/src/types/debug-utils.d.ts b/packages/dashboard/src/types/debug-utils.d.ts new file mode 100644 index 00000000000..6ea19c448c0 --- /dev/null +++ b/packages/dashboard/src/types/debug-utils.d.ts @@ -0,0 +1 @@ +declare module "@truffle/debug-utils"; diff --git a/packages/dashboard/src/types/debugger.d.ts b/packages/dashboard/src/types/debugger.d.ts new file mode 100644 index 00000000000..17487d4a27e --- /dev/null +++ b/packages/dashboard/src/types/debugger.d.ts @@ -0,0 +1 @@ +declare module "@truffle/debugger"; diff --git a/packages/dashboard/src/types/highlightjs-solidity.d.ts b/packages/dashboard/src/types/highlightjs-solidity.d.ts new file mode 100644 index 00000000000..e9dca986f6d --- /dev/null +++ b/packages/dashboard/src/types/highlightjs-solidity.d.ts @@ -0,0 +1 @@ +declare module "highlightjs-solidity"; diff --git a/packages/dashboard/src/utils/constants.ts b/packages/dashboard/src/utils/constants.ts index 8a1215b9320..b72d4b30039 100644 --- a/packages/dashboard/src/utils/constants.ts +++ b/packages/dashboard/src/utils/constants.ts @@ -2,6 +2,7 @@ import chainIDtoNameJson from "src/assets/chainIDtoName.json"; export const EMOTION_KEY = "trfl"; export const COLOR_SCHEME_KEY = "trfl.dash.color-scheme"; +export const etherscanApiKeyName = "trfl.dash.etherscan-api-key"; export const decodableRpcMethodsArr = [ "eth_sendTransaction", @@ -10,7 +11,7 @@ export const decodableRpcMethodsArr = [ "eth_signTypedData_v4" ] as const; export const decodableRpcMethods = new Set(decodableRpcMethodsArr); -export type DecodableRpcMethod = typeof decodableRpcMethodsArr[number]; +export type DecodableRpcMethod = (typeof decodableRpcMethodsArr)[number]; export const interactiveRpcMethodsArr = [ ...decodableRpcMethodsArr, @@ -19,14 +20,14 @@ export const interactiveRpcMethodsArr = [ "eth_signTypedData_v1" ] as const; export const interactiveRpcMethods = new Set(interactiveRpcMethodsArr); -export type InteractiveRpcMethod = typeof interactiveRpcMethodsArr[number]; +export type InteractiveRpcMethod = (typeof interactiveRpcMethodsArr)[number]; export const unsupportedRpcMethodsArr = [ "eth_sign", "eth_signTransaction" ] as const; export const unsupportedRpcMethods = new Set(unsupportedRpcMethodsArr); -export type UnsupportedRpcMethod = typeof unsupportedRpcMethodsArr[number]; +export type UnsupportedRpcMethod = (typeof unsupportedRpcMethodsArr)[number]; export const unsupportedMessageResponse = new Map( [ diff --git a/packages/dashboard/webpack.config.ts b/packages/dashboard/webpack.config.ts index 66a02efbc00..29a8bff0fae 100644 --- a/packages/dashboard/webpack.config.ts +++ b/packages/dashboard/webpack.config.ts @@ -79,6 +79,10 @@ const config: webpack.Configuration = { { test: /\.(png|ttf)$/, type: "asset/resource" + }, + { + test: /\.css$/, + use: ["style-loader", "css-loader"] } ] }, diff --git a/yarn.lock b/yarn.lock index 886d60acee2..f666f01d0b3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2029,7 +2029,7 @@ dependencies: regenerator-runtime "^0.13.4" -"@babel/runtime@^7.10.2", "@babel/runtime@^7.13.10", "@babel/runtime@^7.16.3", "@babel/runtime@^7.17.2", "@babel/runtime@^7.18.3", "@babel/runtime@^7.18.9", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.7": +"@babel/runtime@^7.10.2", "@babel/runtime@^7.13.10", "@babel/runtime@^7.16.3", "@babel/runtime@^7.17.2", "@babel/runtime@^7.18.3", "@babel/runtime@^7.18.9", "@babel/runtime@^7.8.7": version "7.18.9" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.18.9.tgz#b4fcfce55db3d2e5e080d2490f608a3b9f407f4a" integrity sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw== @@ -3989,34 +3989,33 @@ dependencies: fast-check "^3.0.0" -"@floating-ui/core@^0.7.3": - version "0.7.3" - resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-0.7.3.tgz#d274116678ffae87f6b60e90f88cc4083eefab86" - integrity sha512-buc8BXHmG9l82+OQXOFU3Kr2XQx9ys01U/Q9HMIrZ300iLc8HLMgh7dcCqgYzAzf4BkoQvDcXf5Y+CuEZ5JBYg== +"@floating-ui/core@^1.2.2": + version "1.2.2" + resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.2.2.tgz#66f62cf1b7de2ed23a09c101808536e68caffaec" + integrity sha512-FaO9KVLFnxknZaGWGmNtjD2CVFuc0u4yeGEofoyXO2wgRA7fLtkngT6UB0vtWQWuhH3iMTZZ/Y89CMeyGfn8pA== -"@floating-ui/dom@^0.5.3": - version "0.5.4" - resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-0.5.4.tgz#4eae73f78bcd4bd553ae2ade30e6f1f9c73fe3f1" - integrity sha512-419BMceRLq0RrmTSDxn8hf9R3VCJv2K9PUfugh5JyEFmdjzDo+e8U5EdR8nzKq8Yj1htzLm3b6eQEEam3/rrtg== +"@floating-ui/dom@^1.2.1": + version "1.2.3" + resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.2.3.tgz#8dc6fbf799fbb5c29f705b54bdd51f3ab0ee03a2" + integrity sha512-lK9cZUrHSJLMVAdCvDqs6Ug8gr0wmqksYiaoj/bxj2gweRQkSuhg2/V6Jswz2KiQ0RAULbqw1oQDJIMpQ5GfGA== dependencies: - "@floating-ui/core" "^0.7.3" + "@floating-ui/core" "^1.2.2" -"@floating-ui/react-dom-interactions@0.6.6": - version "0.6.6" - resolved "https://registry.yarnpkg.com/@floating-ui/react-dom-interactions/-/react-dom-interactions-0.6.6.tgz#8542e8c4bcbee2cd0d512de676c6a493e0a2d168" - integrity sha512-qnao6UPjSZNHnXrF+u4/n92qVroQkx0Umlhy3Avk1oIebm/5ee6yvDm4xbHob0OjY7ya8WmUnV3rQlPwX3Atwg== +"@floating-ui/react-dom@^1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-1.3.0.tgz#4d35d416eb19811c2b0e9271100a6aa18c1579b3" + integrity sha512-htwHm67Ji5E/pROEAr7f8IKFShuiCKHwUC/UY4vC3I5jiSvGFAYnSYiZO5MlGmads+QqvUkR9ANHEguGrDv72g== dependencies: - "@floating-ui/react-dom" "^0.7.2" - aria-hidden "^1.1.3" - use-isomorphic-layout-effect "^1.1.1" + "@floating-ui/dom" "^1.2.1" -"@floating-ui/react-dom@^0.7.2": - version "0.7.2" - resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-0.7.2.tgz#0bf4ceccb777a140fc535c87eb5d6241c8e89864" - integrity sha512-1T0sJcpHgX/u4I1OzIEhlcrvkUN8ln39nz7fMoE/2HDHrPiMFoOGR7++GYyfUmIQHkkrTinaeQsO3XWubjSvGg== +"@floating-ui/react@^0.19.1": + version "0.19.2" + resolved "https://registry.yarnpkg.com/@floating-ui/react/-/react-0.19.2.tgz#c6e4d2097ed0dca665a7c042ddf9cdecc95e9412" + integrity sha512-JyNk4A0Ezirq8FlXECvRtQOX/iBe5Ize0W/pLkrZjfHW9GUV7Xnq6zm6fyZuQzaHHqEnVizmvlA96e1/CkZv+w== dependencies: - "@floating-ui/dom" "^0.5.3" - use-isomorphic-layout-effect "^1.1.1" + "@floating-ui/react-dom" "^1.3.0" + aria-hidden "^1.1.3" + tabbable "^6.0.1" "@ganache/console.log@0.3.0": version "0.3.0" @@ -5460,50 +5459,50 @@ npmlog "^6.0.2" write-file-atomic "^4.0.1" -"@mantine/core@^5.0.0": - version "5.1.6" - resolved "https://registry.yarnpkg.com/@mantine/core/-/core-5.1.6.tgz#98c984bf08ceeb8684a8dc7146034c85e35de9f7" - integrity sha512-ZG/ccTc+LcxcUahxMu4m3hOkAss8LetETFbZ5zsDotJTvij2lzMzNcuPebLeyxNt714VEWIXNzIUotNtGXDSGg== +"@mantine/core@^5.10.5": + version "5.10.5" + resolved "https://registry.yarnpkg.com/@mantine/core/-/core-5.10.5.tgz#071e14dcf8b94a36d0243f1f4b30305ac0074afd" + integrity sha512-F4tqHSEVM9D6/iSqHfPda+Xl5XgSEPHAAkT01Zwzj4Jnbd10qGrlqr/SFUop2CIcuKYnmra9XltUahUPXBC2BQ== dependencies: - "@floating-ui/react-dom-interactions" "0.6.6" - "@mantine/styles" "5.1.6" - "@mantine/utils" "5.1.6" - "@radix-ui/react-scroll-area" "1.0.0" + "@floating-ui/react" "^0.19.1" + "@mantine/styles" "5.10.5" + "@mantine/utils" "5.10.5" + "@radix-ui/react-scroll-area" "1.0.2" react-textarea-autosize "8.3.4" -"@mantine/hooks@^5.0.0": - version "5.1.6" - resolved "https://registry.yarnpkg.com/@mantine/hooks/-/hooks-5.1.6.tgz#07d4850d3444526268a749c965ce64eea8cb7583" - integrity sha512-wD+Cx9W00YQchudIvkiEy52e/UCB0EhTkJs2fr2JbTlfKCWfWSilE/CPbLz6NWmK58bbyB/qjdKUDwXV6qyasw== +"@mantine/hooks@^5.10.5": + version "5.10.5" + resolved "https://registry.yarnpkg.com/@mantine/hooks/-/hooks-5.10.5.tgz#568586a0fa649be46f057ddc920bf98761017ffb" + integrity sha512-hFQp71QZDfivPzfIUOQZfMKLiOL/Cn2EnzacRlbUr55myteTfzYN8YMt+nzniE/6c4IRopFHEAdbKEtfyQc6kg== -"@mantine/notifications@^5.0.0": - version "5.1.6" - resolved "https://registry.yarnpkg.com/@mantine/notifications/-/notifications-5.1.6.tgz#6311c928e821fdee6963048a698fce465482a589" - integrity sha512-JT0nLOoNO/NDuB2Xn4kVOSo+4Y4F+ogrWLN7qHgpmZlRsxc/8S1DFwgubTMwvJR38AHrOVaqp1hFsqdHDww0Ow== +"@mantine/notifications@^5.10.5": + version "5.10.5" + resolved "https://registry.yarnpkg.com/@mantine/notifications/-/notifications-5.10.5.tgz#2f3f2d013ce4637e64e935aa5dd8c1df1a7acec0" + integrity sha512-IzTAXA7Zb9DcI94Mv5O2OinhLmI7fvs/VutDw9uCpp6OHtLuF/XN1d262jrsGhMZT0c4nuUsotSLFZF3GWZwXg== dependencies: - "@mantine/utils" "5.1.6" + "@mantine/utils" "5.10.5" react-transition-group "4.4.2" -"@mantine/prism@^5.0.0": - version "5.1.6" - resolved "https://registry.yarnpkg.com/@mantine/prism/-/prism-5.1.6.tgz#efa46e5d8d8466b0111dbdc40415aa896b82aca1" - integrity sha512-13y8Ri4GxwdFcnan5WDzHDvN9LxzdnHLi+KDYfC5FM34SDp1//IuA5EQk3TZ9Jb9FD0rLwOX/vePE+1oEcrh9w== +"@mantine/prism@^5.10.5": + version "5.10.5" + resolved "https://registry.yarnpkg.com/@mantine/prism/-/prism-5.10.5.tgz#3e42e89a46b7f8d4d8953814f62785c3b5fcc81c" + integrity sha512-xwe3RE6wg0/KuCBH/MTTQIOltBLTELSfUzHa5/66q4aPdgRPnbzOVxiH/ytndDuqJd8MKpyo25M+3nPAtq2O4A== dependencies: - "@mantine/utils" "5.1.6" + "@mantine/utils" "5.10.5" prism-react-renderer "^1.2.1" -"@mantine/styles@5.1.6": - version "5.1.6" - resolved "https://registry.yarnpkg.com/@mantine/styles/-/styles-5.1.6.tgz#cfb4a33d4c38ffb499d51cd0af35abd436ebe831" - integrity sha512-7d112DIHauP+X0JaVh0uzlpVndWqw/muaz77C5hLlA8yP7gVuRWOhEqJDuTnZzNtj/utIJJDAyVM2+DHFqtQtg== +"@mantine/styles@5.10.5": + version "5.10.5" + resolved "https://registry.yarnpkg.com/@mantine/styles/-/styles-5.10.5.tgz#ace82a71b4fe3d14ee14638f1735d5680d93d36d" + integrity sha512-0NXk8c/XGzuTUkZc6KceF2NaTCMEu5mHR4ru0x+ttb9DGnLpHuGWduTHjSfr4hl6eAJgedD0zauO+VAhDzO9zA== dependencies: clsx "1.1.1" csstype "3.0.9" -"@mantine/utils@5.1.6": - version "5.1.6" - resolved "https://registry.yarnpkg.com/@mantine/utils/-/utils-5.1.6.tgz#992cd1e8850467533aa88dc5fd933650674abf5e" - integrity sha512-y7va2keQ+TPwn2j668r889NhQWO0OhSvaxT5P8n2iBaTyPg+Xp5Jm2IE5UjI+OrZErrgmQq1sxbfg6v5vdK+0A== +"@mantine/utils@5.10.5": + version "5.10.5" + resolved "https://registry.yarnpkg.com/@mantine/utils/-/utils-5.10.5.tgz#ad620d714e545c6efb7f69d94ce46e3fd2fe01fb" + integrity sha512-FGMq4dGs5HhDAtI0z46uzxzKKPmZ3h5uKUyKg1ZHoFR1mBtcUMbB6FylFmHqKFRWlJ5IXqX9dwmiVrLYUOfTmA== "@metamask/eth-sig-util@4.0.1", "@metamask/eth-sig-util@^4.0.0": version "4.0.1" @@ -6198,18 +6197,18 @@ "@radix-ui/react-compose-refs" "1.0.0" "@radix-ui/react-use-layout-effect" "1.0.0" -"@radix-ui/react-primitive@1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@radix-ui/react-primitive/-/react-primitive-1.0.0.tgz#376cd72b0fcd5e0e04d252ed33eb1b1f025af2b0" - integrity sha512-EyXe6mnRlHZ8b6f4ilTDrXmkLShICIuOTTj0GX4w1rp+wSxf3+TD05u1UOITC8VsJ2a9nwHvdXtOXEOl0Cw/zQ== +"@radix-ui/react-primitive@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-primitive/-/react-primitive-1.0.1.tgz#c1ebcce283dd2f02e4fbefdaa49d1cb13dbc990a" + integrity sha512-fHbmislWVkZaIdeF6GZxF0A/NH/3BjrGIYj+Ae6eTmTCr7EB0RQAAVEiqsXK6p3/JcRqVSBQoceZroj30Jj3XA== dependencies: "@babel/runtime" "^7.13.10" - "@radix-ui/react-slot" "1.0.0" + "@radix-ui/react-slot" "1.0.1" -"@radix-ui/react-scroll-area@1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@radix-ui/react-scroll-area/-/react-scroll-area-1.0.0.tgz#10d0262a52266af528798f36947145f7e3a3a52c" - integrity sha512-3SNFukAjS5remgtpAVR9m3Zgo23ZojBZ8V3TCyR3A+56x2mtVqKlPV4+e8rScZUFMuvtbjIdQCmsJBFBazKZig== +"@radix-ui/react-scroll-area@1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@radix-ui/react-scroll-area/-/react-scroll-area-1.0.2.tgz#26c906d351b56835c0301126b24574c9e9c7b93b" + integrity sha512-k8VseTxI26kcKJaX0HPwkvlNBPTs56JRdYzcZ/vzrNUkDlvXBy8sMc7WvCpYzZkHgb+hd72VW9MqkqecGtuNgg== dependencies: "@babel/runtime" "^7.13.10" "@radix-ui/number" "1.0.0" @@ -6218,14 +6217,14 @@ "@radix-ui/react-context" "1.0.0" "@radix-ui/react-direction" "1.0.0" "@radix-ui/react-presence" "1.0.0" - "@radix-ui/react-primitive" "1.0.0" + "@radix-ui/react-primitive" "1.0.1" "@radix-ui/react-use-callback-ref" "1.0.0" "@radix-ui/react-use-layout-effect" "1.0.0" -"@radix-ui/react-slot@1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-1.0.0.tgz#7fa805b99891dea1e862d8f8fbe07f4d6d0fd698" - integrity sha512-3mrKauI/tWXo1Ll+gN5dHcxDPdm/Df1ufcDLCecn+pnCIVcdWE7CujXo8QaXOWRJyZyQWWbpB8eFwHzWXlv5mQ== +"@radix-ui/react-slot@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-1.0.1.tgz#e7868c669c974d649070e9ecbec0b367ee0b4d81" + integrity sha512-avutXAFL1ehGvAXtPquu0YK5oz6ctS474iM3vNGQIkswrVhdrS52e3uoMQBzZhNRAIE0jBnUyXWNmSjGHhCFcw== dependencies: "@babel/runtime" "^7.13.10" "@radix-ui/react-compose-refs" "1.0.0" @@ -6288,6 +6287,11 @@ resolved "https://registry.yarnpkg.com/@redux-saga/types/-/types-1.1.0.tgz#0e81ce56b4883b4b2a3001ebe1ab298b84237204" integrity sha512-afmTuJrylUU/0OtqzaRkbyYFFNgCF73Bvel/sw90pvGrWIZ+vyoIJqA6eMSoA6+nb443kTmulmBtC9NerXboNg== +"@remix-run/router@1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.5.0.tgz#57618e57942a5f0131374a9fdb0167e25a117fdc" + integrity sha512-bkUDCp8o1MvFO+qxkODcbhSqRa6P2GXgrGZVpt0dCXNW2HCSCqYI0ZoAqEOSAjRWmmlKcYgFvN4B4S+zo/f8kg== + "@rushstack/eslint-patch@^1.1.0": version "1.1.4" resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.1.4.tgz#0c8b74c50f29ee44f423f7416829c0bf8bb5eb27" @@ -7005,6 +7009,13 @@ dependencies: graphql "*" +"@types/hast@^2.0.0": + version "2.3.4" + resolved "https://registry.yarnpkg.com/@types/hast/-/hast-2.3.4.tgz#8aa5ef92c117d20d974a82bdfb6a648b08c0bafc" + integrity sha512-wLEm0QvaoawEDoTRwzTXp4b4jpwiJDvR5KMnFnVodm3scufTlBOWRD6N1OBf9TZMhjlNsSfcO5V+7AF4+Vy+9g== + dependencies: + "@types/unist" "*" + "@types/html-minifier-terser@^6.0.0": version "6.1.0" resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz#4fc33a00c1d0c16987b1a20cf92d20614c55ac35" @@ -7263,6 +7274,11 @@ resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== +"@types/parse5@^6.0.0": + version "6.0.3" + resolved "https://registry.yarnpkg.com/@types/parse5/-/parse5-6.0.3.tgz#705bb349e789efa06f43f128cef51240753424cb" + integrity sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g== + "@types/pbkdf2@^3.0.0": version "3.0.0" resolved "https://registry.yarnpkg.com/@types/pbkdf2/-/pbkdf2-3.0.0.tgz#5d9ca5f12a78a08cc89ad72883ad4a30af359229" @@ -7639,6 +7655,11 @@ resolved "https://registry.yarnpkg.com/@types/underscore/-/underscore-1.9.4.tgz#22d1a3e6b494608e430221ec085fa0b7ccee7f33" integrity sha512-CjHWEMECc2/UxOZh0kpiz3lEyX2Px3rQS9HzD20lxMvx571ivOBQKeLnqEjxUY0BMgp6WJWo/pQLRBwMW5v4WQ== +"@types/unist@*", "@types/unist@^2.0.0": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.6.tgz#250a7b16c3b91f672a24552ec64678eeb1d3a08d" + integrity sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ== + "@types/use-sync-external-store@^0.0.3": version "0.0.3" resolved "https://registry.yarnpkg.com/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz#b6725d5f4af24ace33b36fafd295136e75509f43" @@ -9884,6 +9905,11 @@ backoff@^2.5.0: dependencies: precond "0.2" +bail@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/bail/-/bail-2.0.2.tgz#d26f5cd8fe5d6f832a31517b9f7c356040ba6d5d" + integrity sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw== + balanced-match@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" @@ -10697,6 +10723,11 @@ cbor@^5.2.0: bignumber.js "^9.0.1" nofilter "^1.0.4" +ccount@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/ccount/-/ccount-2.0.1.tgz#17a3bf82302e0870d6da43a01311a8bc02a3ecf5" + integrity sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg== + cfonts@^2.8.6: version "2.9.1" resolved "https://registry.yarnpkg.com/cfonts/-/cfonts-2.9.1.tgz#90befec39cf0c779cbf82859c342aad44df1bc06" @@ -10797,6 +10828,16 @@ char-regex@^1.0.2: resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== +character-entities-html4@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/character-entities-html4/-/character-entities-html4-2.1.0.tgz#1f1adb940c971a4b22ba39ddca6b618dc6e56b2b" + integrity sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA== + +character-entities-legacy@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz#76bc83a90738901d7bc223a9e93759fdd560125b" + integrity sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ== + chardet@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" @@ -11224,6 +11265,11 @@ combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: dependencies: delayed-stream "~1.0.0" +comma-separated-tokens@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz#4e89c9458acb61bc8fef19f4529973b2392839ee" + integrity sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg== + command-exists@^1.2.8: version "1.2.8" resolved "https://registry.yarnpkg.com/command-exists/-/command-exists-1.2.8.tgz#715acefdd1223b9c9b37110a149c6392c2852291" @@ -11773,6 +11819,20 @@ crypto-browserify@^3.12.0: randombytes "^2.0.0" randomfill "^1.0.3" +css-loader@^6.8.1: + version "6.8.1" + resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.8.1.tgz#0f8f52699f60f5e679eab4ec0fcd68b8e8a50a88" + integrity sha512-xDAXtEVGlD0gJ07iclwWVkLoZOpEvAWaSyf6W18S2pOC//K8+qUDIx8IIT3D+HjnmkJPQeesOPv5aiUaJsCM2g== + dependencies: + icss-utils "^5.1.0" + postcss "^8.4.21" + postcss-modules-extract-imports "^3.0.0" + postcss-modules-local-by-default "^4.0.3" + postcss-modules-scope "^3.0.0" + postcss-modules-values "^4.0.0" + postcss-value-parser "^4.2.0" + semver "^7.3.8" + css-select@^4.1.3: version "4.3.0" resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.3.0.tgz#db7129b2846662fd8628cfc496abb2b59e41529b" @@ -11804,6 +11864,11 @@ css-what@^6.0.1: resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4" integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw== +cssesc@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" + integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== + cssfilter@0.0.10: version "0.0.10" resolved "https://registry.yarnpkg.com/cssfilter/-/cssfilter-0.0.10.tgz#c6d2672632a2e5c83e013e6864a42ce8defd20ae" @@ -14299,7 +14364,7 @@ extend-shallow@^3.0.0, extend-shallow@^3.0.2: assign-symbols "^1.0.0" is-extendable "^1.0.1" -extend@~3.0.2: +extend@^3.0.0, extend@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== @@ -14444,6 +14509,13 @@ fastq@^1.6.0: dependencies: reusify "^1.0.4" +fault@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/fault/-/fault-2.0.1.tgz#d47ca9f37ca26e4bd38374a7c500b5a384755b6c" + integrity sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ== + dependencies: + format "^0.2.0" + faye-websocket@^0.11.3: version "0.11.4" resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.4.tgz#7f0d9275cfdd86a1c963dc8b65fcc451edcbb1da" @@ -14772,6 +14844,11 @@ format-util@^1.0.3: resolved "https://registry.yarnpkg.com/format-util/-/format-util-1.0.3.tgz#032dca4a116262a12c43f4c3ec8566416c5b2d95" integrity sha1-Ay3KShFiYqEsQ/TD7IVmQWxbLZU= +format@^0.2.0: + version "0.2.2" + resolved "https://registry.yarnpkg.com/format/-/format-0.2.2.tgz#d6170107e9efdc4ed30c9dc39016df942b5cb58b" + integrity sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww== + forwarded@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" @@ -15813,6 +15890,88 @@ hasha@^5.0.0: is-stream "^2.0.0" type-fest "^0.8.0" +hast-util-from-parse5@^7.0.0: + version "7.1.1" + resolved "https://registry.yarnpkg.com/hast-util-from-parse5/-/hast-util-from-parse5-7.1.1.tgz#1887b4dd4e19f29d9c48c2e1c8bfeaac13a1f3a0" + integrity sha512-R6PoNcUs89ZxLJmMWsVbwSWuz95/9OriyQZ3e2ybwqGsRXzhA6gv49rgGmQvLbZuSNDv9fCg7vV7gXUsvtUFaA== + dependencies: + "@types/hast" "^2.0.0" + "@types/unist" "^2.0.0" + hastscript "^7.0.0" + property-information "^6.0.0" + vfile "^5.0.0" + vfile-location "^4.0.0" + web-namespaces "^2.0.0" + +hast-util-parse-selector@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/hast-util-parse-selector/-/hast-util-parse-selector-3.1.1.tgz#25ab00ae9e75cbc62cf7a901f68a247eade659e2" + integrity sha512-jdlwBjEexy1oGz0aJ2f4GKMaVKkA9jwjr4MjAAI22E5fM/TXVZHuS5OpONtdeIkRKqAaryQ2E9xNQxijoThSZA== + dependencies: + "@types/hast" "^2.0.0" + +hast-util-raw@^7.0.0: + version "7.2.3" + resolved "https://registry.yarnpkg.com/hast-util-raw/-/hast-util-raw-7.2.3.tgz#dcb5b22a22073436dbdc4aa09660a644f4991d99" + integrity sha512-RujVQfVsOrxzPOPSzZFiwofMArbQke6DJjnFfceiEbFh7S05CbPt0cYN+A5YeD3pso0JQk6O1aHBnx9+Pm2uqg== + dependencies: + "@types/hast" "^2.0.0" + "@types/parse5" "^6.0.0" + hast-util-from-parse5 "^7.0.0" + hast-util-to-parse5 "^7.0.0" + html-void-elements "^2.0.0" + parse5 "^6.0.0" + unist-util-position "^4.0.0" + unist-util-visit "^4.0.0" + vfile "^5.0.0" + web-namespaces "^2.0.0" + zwitch "^2.0.0" + +hast-util-to-html@^8.0.0: + version "8.0.4" + resolved "https://registry.yarnpkg.com/hast-util-to-html/-/hast-util-to-html-8.0.4.tgz#0269ef33fa3f6599b260a8dc94f733b8e39e41fc" + integrity sha512-4tpQTUOr9BMjtYyNlt0P50mH7xj0Ks2xpo8M943Vykljf99HW6EzulIoJP1N3eKOSScEHzyzi9dm7/cn0RfGwA== + dependencies: + "@types/hast" "^2.0.0" + "@types/unist" "^2.0.0" + ccount "^2.0.0" + comma-separated-tokens "^2.0.0" + hast-util-raw "^7.0.0" + hast-util-whitespace "^2.0.0" + html-void-elements "^2.0.0" + property-information "^6.0.0" + space-separated-tokens "^2.0.0" + stringify-entities "^4.0.0" + zwitch "^2.0.4" + +hast-util-to-parse5@^7.0.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/hast-util-to-parse5/-/hast-util-to-parse5-7.1.0.tgz#c49391bf8f151973e0c9adcd116b561e8daf29f3" + integrity sha512-YNRgAJkH2Jky5ySkIqFXTQiaqcAtJyVE+D5lkN6CdtOqrnkLfGYYrEcKuHOJZlp+MwjSwuD3fZuawI+sic/RBw== + dependencies: + "@types/hast" "^2.0.0" + comma-separated-tokens "^2.0.0" + property-information "^6.0.0" + space-separated-tokens "^2.0.0" + web-namespaces "^2.0.0" + zwitch "^2.0.0" + +hast-util-whitespace@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/hast-util-whitespace/-/hast-util-whitespace-2.0.1.tgz#0ec64e257e6fc216c7d14c8a1b74d27d650b4557" + integrity sha512-nAxA0v8+vXSBDt3AnRUNjyRIQ0rD+ntpbAp4LnPkumc5M9yUbSMa4XDU9Q6etY4f1Wp4bNgvc1yjiZtsTTrSng== + +hastscript@^7.0.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/hastscript/-/hastscript-7.2.0.tgz#0eafb7afb153d047077fa2a833dc9b7ec604d10b" + integrity sha512-TtYPq24IldU8iKoJQqvZOuhi5CyCQRAbvDOX0x1eW6rsHSxa/1i2CCiptNTotGHJ3VoHRGmqiv6/D3q113ikkw== + dependencies: + "@types/hast" "^2.0.0" + comma-separated-tokens "^2.0.0" + hast-util-parse-selector "^3.0.0" + property-information "^6.0.0" + space-separated-tokens "^2.0.0" + he@1.2.0, he@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" @@ -15836,18 +15995,16 @@ highlight.js@^10.4.1: resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.4.1.tgz#d48fbcf4a9971c4361b3f95f302747afe19dbad0" integrity sha512-yR5lWvNz7c85OhVAEAeFhVCc/GV4C30Fjzc/rCP0aCWzc1UUOPUk55dK/qdwTZHBvMZo+eZ2jpk62ndX/xMFlg== +highlight.js@~11.7.0: + version "11.7.0" + resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.7.0.tgz#3ff0165bc843f8c9bce1fd89e2fda9143d24b11e" + integrity sha512-1rRqesRFhMO/PRF+G86evnyJkCgaZFOI+Z6kdj15TA18funfoqJXvgPCLSf0SWq3SRfg1j3HlDs8o4s3EGq1oQ== + highlightjs-solidity@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/highlightjs-solidity/-/highlightjs-solidity-2.0.6.tgz#e7a702a2b05e0a97f185e6ba39fd4846ad23a990" integrity sha512-DySXWfQghjm2l6a/flF+cteroJqD4gI8GSdL4PtvxZSsAHie8m3yVe2JFoRg03ROKT6hp2Lc/BxXkqerNmtQYg== -history@^5.2.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/history/-/history-5.3.0.tgz#1548abaa245ba47992f063a0783db91ef201c73b" - integrity sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ== - dependencies: - "@babel/runtime" "^7.7.6" - hkts@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/hkts/-/hkts-0.3.1.tgz#444e33ae89138b46cbdd669d4cfed358ec0dd7db" @@ -15943,6 +16100,11 @@ html-minifier-terser@^6.0.2: relateurl "^0.2.7" terser "^5.10.0" +html-void-elements@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/html-void-elements/-/html-void-elements-2.0.1.tgz#29459b8b05c200b6c5ee98743c41b979d577549f" + integrity sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A== + html-webpack-plugin@^5.5.0: version "5.5.0" resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-5.5.0.tgz#c3911936f57681c1f9f4d8b68c158cd9dfe52f50" @@ -16157,6 +16319,11 @@ iconv-lite@^0.6.2: dependencies: safer-buffer ">= 2.1.2 < 3.0.0" +icss-utils@^5.0.0, icss-utils@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae" + integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA== + idb@^7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/idb/-/idb-7.1.1.tgz#d910ded866d32c7ced9befc5bfdf36f572ced72b" @@ -16555,7 +16722,7 @@ is-buffer@^1.1.5: resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== -is-buffer@^2.0.5: +is-buffer@^2.0.0, is-buffer@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.5.tgz#ebc252e400d22ff8d77fa09888821a24a658c191" integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ== @@ -16877,6 +17044,11 @@ is-plain-obj@^3.0.0: resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-3.0.0.tgz#af6f2ea14ac5a646183a5bbdb5baabbc156ad9d7" integrity sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA== +is-plain-obj@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-4.1.0.tgz#d65025edec3657ce032fd7db63c97883eaed71f0" + integrity sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg== + is-plain-object@^2.0.3, is-plain-object@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" @@ -18930,6 +19102,15 @@ lowercase-keys@^3.0.0: resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-3.0.0.tgz#c5e7d442e37ead247ae9db117a9d0a467c89d4f2" integrity sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ== +lowlight@^2.8.1: + version "2.8.1" + resolved "https://registry.yarnpkg.com/lowlight/-/lowlight-2.8.1.tgz#5f54016ebd1b2f66b3d0b94d10ef6dd5df4f2e42" + integrity sha512-HCaGL61RKc1MYzEYn3rFoGkK0yslzCVDFJEanR19rc2L0mb8i58XM55jSRbzp9jcQrFzschPlwooC0vuNitk8Q== + dependencies: + "@types/hast" "^2.0.0" + fault "^2.0.0" + highlight.js "~11.7.0" + lru-cache@6.0.0, lru-cache@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" @@ -20505,11 +20686,16 @@ object-inspect@^1.11.0: resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.11.1.tgz#d4bd7d7de54b9a75599f59a00bd698c1f1c6549b" integrity sha512-If7BjFlpkzzBeV1cqgT3OSWT3azyoxDGajR+iGnFBfVV2EWyDyWaZZW2ERDjUaY2QM8i5jI3Sj7mhsM4DDAqWA== -object-inspect@^1.12.0, object-inspect@^1.12.2: +object-inspect@^1.12.0: version "1.12.2" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.2.tgz#c0641f26394532f28ab8d796ab954e43c009a8ea" integrity sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ== +object-inspect@^1.12.2: + version "1.12.3" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.3.tgz#ba62dffd67ee256c8c086dfae69e016cd1f198b9" + integrity sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g== + object-inspect@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.7.0.tgz#f4f6bd181ad77f006b5ece60bd0b6f398ff74a67" @@ -21163,7 +21349,7 @@ parse5@^5.1.1: resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.1.tgz#f68e4e5ba1852ac2cadc00f4555fff6c2abb6178" integrity sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug== -parse5@^6.0.1: +parse5@^6.0.0, parse5@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== @@ -21444,6 +21630,47 @@ posix-character-classes@^0.1.0: resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= +postcss-modules-extract-imports@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz#cda1f047c0ae80c97dbe28c3e76a43b88025741d" + integrity sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw== + +postcss-modules-local-by-default@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.3.tgz#b08eb4f083050708998ba2c6061b50c2870ca524" + integrity sha512-2/u2zraspoACtrbFRnTijMiQtb4GW4BvatjaG/bCjYQo8kLTdevCUlwuBHx2sCnSyrI3x3qj4ZK1j5LQBgzmwA== + dependencies: + icss-utils "^5.0.0" + postcss-selector-parser "^6.0.2" + postcss-value-parser "^4.1.0" + +postcss-modules-scope@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz#9ef3151456d3bbfa120ca44898dfca6f2fa01f06" + integrity sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg== + dependencies: + postcss-selector-parser "^6.0.4" + +postcss-modules-values@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz#d7c5e7e68c3bb3c9b27cbf48ca0bb3ffb4602c9c" + integrity sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ== + dependencies: + icss-utils "^5.0.0" + +postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4: + version "6.0.13" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz#d05d8d76b1e8e173257ef9d60b706a8e5e99bf1b" + integrity sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ== + dependencies: + cssesc "^3.0.0" + util-deprecate "^1.0.2" + +postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" + integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== + postcss-values-parser@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/postcss-values-parser/-/postcss-values-parser-2.0.1.tgz#da8b472d901da1e205b47bdc98637b9e9e550e5f" @@ -21471,7 +21698,7 @@ postcss@^8.1.7, postcss@^8.2.13: picocolors "^1.0.0" source-map-js "^1.0.2" -postcss@^8.4.23: +postcss@^8.4.21, postcss@^8.4.23: version "8.4.24" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.24.tgz#f714dba9b2284be3cc07dbd2fc57ee4dc972d2df" integrity sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg== @@ -21977,6 +22204,11 @@ prop-types@^15.8.1: object-assign "^4.1.1" react-is "^16.13.1" +property-information@^6.0.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/property-information/-/property-information-6.2.0.tgz#b74f522c31c097b5149e3c3cb8d7f3defd986a1d" + integrity sha512-kma4U7AFCTwpqq5twzC1YVIDXSqg6qQK6JN0smOw8fgRy1OkMi0CYSzFmsy6dnqSenamAtj0CyXMUJ1Mf6oROg== + proto-list@~1.2.1: version "1.2.4" resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" @@ -22267,20 +22499,20 @@ react-refresh@^0.14.0: resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.0.tgz#4e02825378a5f227079554d4284889354e5f553e" integrity sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ== -react-router-dom@^6.3.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.3.0.tgz#a0216da813454e521905b5fa55e0e5176123f43d" - integrity sha512-uaJj7LKytRxZNQV8+RbzJWnJ8K2nPsOOEuX7aQstlMZKQT0164C+X2w6bnkqU3sjtLvpd5ojrezAyfZ1+0sStw== +react-router-dom@^6.10.0: + version "6.10.0" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.10.0.tgz#090ddc5c84dc41b583ce08468c4007c84245f61f" + integrity sha512-E5dfxRPuXKJqzwSe/qGcqdwa18QiWC6f3H3cWXM24qj4N0/beCIf/CWTipop2xm7mR0RCS99NnaqPNjHtrAzCg== dependencies: - history "^5.2.0" - react-router "6.3.0" + "@remix-run/router" "1.5.0" + react-router "6.10.0" -react-router@6.3.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.3.0.tgz#3970cc64b4cb4eae0c1ea5203a80334fdd175557" - integrity sha512-7Wh1DzVQ+tlFjkeo+ujvjSqSJmkt1+8JO+T5xklPlgrh70y7ogx75ODRW0ThWhY7S+6yEDks8TYrtQe/aoboBQ== +react-router@6.10.0: + version "6.10.0" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.10.0.tgz#230f824fde9dd0270781b5cb497912de32c0a971" + integrity sha512-Nrg0BWpQqrC3ZFFkyewrflCud9dio9ME3ojHCF/WLsprJVzkq3q3UeEhMCAW1dobjeGbWgjNn/PVF6m46ANxXQ== dependencies: - history "^5.2.0" + "@remix-run/router" "1.5.0" react-textarea-autosize@8.3.4: version "8.3.4" @@ -22674,6 +22906,15 @@ regjsparser@^0.7.0: dependencies: jsesc "~0.5.0" +rehype-stringify@^9.0.3: + version "9.0.3" + resolved "https://registry.yarnpkg.com/rehype-stringify/-/rehype-stringify-9.0.3.tgz#70e3bd6d4d29e7acf36b802deed350305d2c3c17" + integrity sha512-kWiZ1bgyWlgOxpqD5HnxShKAdXtb2IUljn3hQAhySeak6IOQPPt6DeGnsIh4ixm7yKJWzm8TXFuC/lPfcWHJqw== + dependencies: + "@types/hast" "^2.0.0" + hast-util-to-html "^8.0.0" + unified "^10.0.0" + relateurl@^0.2.7: version "0.2.7" resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9" @@ -23309,7 +23550,7 @@ semver@7.5.2: dependencies: lru-cache "^6.0.0" -semver@7.x, semver@^7.0.0, semver@^7.1.1, semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.5.2: +semver@7.x, semver@^7.0.0, semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.7, semver@^7.5.2: version "7.5.3" resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.3.tgz#161ce8c2c6b4b3bdca6caadc9fa3317a4c4fe88e" integrity sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ== @@ -23321,6 +23562,20 @@ semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== +semver@^7.1.1, semver@^7.3.5: + version "7.3.5" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" + integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== + dependencies: + lru-cache "^6.0.0" + +semver@^7.3.8: + version "7.5.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.1.tgz#c90c4d631cf74720e46b21c1d37ea07edfab91ec" + integrity sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw== + dependencies: + lru-cache "^6.0.0" + semver@~5.4.1: version "5.4.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e" @@ -23853,6 +24108,11 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== +space-separated-tokens@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz#1ecd9d2350a3844572c3f4a312bceb018348859f" + integrity sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q== + spark-md5@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/spark-md5/-/spark-md5-3.0.0.tgz#3722227c54e2faf24b1dc6d933cc144e6f71bfef" @@ -24262,6 +24522,14 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" +stringify-entities@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/stringify-entities/-/stringify-entities-4.0.3.tgz#cfabd7039d22ad30f3cc435b0ca2c1574fc88ef8" + integrity sha512-BP9nNHMhhfcMbiuQKCqMjhDP5yBCAxsPu4pHFFzJ6Alo9dZgY4VLDPutXqIjpRiMoKdp7Av85Gr73Q5uH9k7+g== + dependencies: + character-entities-html4 "^2.0.0" + character-entities-legacy "^3.0.0" + stringify-object@^3.2.1: version "3.3.0" resolved "https://registry.yarnpkg.com/stringify-object/-/stringify-object-3.3.0.tgz#703065aefca19300d3ce88af4f5b3956d7556629" @@ -24392,6 +24660,11 @@ strong-log-transformer@^2.1.0: minimist "^1.2.0" through "^2.3.4" +style-loader@^3.3.3: + version "3.3.3" + resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-3.3.3.tgz#bba8daac19930169c0c9c96706749a597ae3acff" + integrity sha512-53BiGLXAcll9maCYtZi2RCQZKa8NQQai5C4horqKyRmHj9H7QmcUyucrH+4KW/gBQbXM2AsB0axoEcFZPlfPcw== + stylis@4.0.13: version "4.0.13" resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.0.13.tgz#f5db332e376d13cc84ecfe5dace9a2a51d954c91" @@ -24513,6 +24786,11 @@ symbol-observable@^1.0.3, symbol-observable@^1.2.0: resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ== +tabbable@^6.0.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-6.1.1.tgz#40cfead5ed11be49043f04436ef924c8890186a0" + integrity sha512-4kl5w+nCB44EVRdO0g/UGoOp3vlwgycUVtkk/7DPyeLZUCuNFFKCFG6/t/DgHLrUPHjrZg6s5tNm+56Q2B0xyg== + tapable@^2.0.0, tapable@^2.1.1, tapable@^2.2.0: version "2.2.1" resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" @@ -24867,6 +25145,11 @@ trim-right@^1.0.1: resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" integrity sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM= +trough@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/trough/-/trough-2.1.0.tgz#0f7b511a4fde65a46f18477ab38849b22c554876" + integrity sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g== + "true-case-path@^2.2.1": version "2.2.1" resolved "https://registry.yarnpkg.com/true-case-path/-/true-case-path-2.2.1.tgz#c5bf04a5bbec3fd118be4084461b3a27c4d796bf" @@ -25274,6 +25557,19 @@ unicode-property-aliases-ecmascript@^2.0.0: resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz#0a36cb9a585c4f6abd51ad1deddb285c165297c8" integrity sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ== +unified@^10.0.0, unified@^10.1.2: + version "10.1.2" + resolved "https://registry.yarnpkg.com/unified/-/unified-10.1.2.tgz#b1d64e55dafe1f0b98bb6c719881103ecf6c86df" + integrity sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q== + dependencies: + "@types/unist" "^2.0.0" + bail "^2.0.0" + extend "^3.0.0" + is-buffer "^2.0.0" + is-plain-obj "^4.0.0" + trough "^2.0.0" + vfile "^5.0.0" + union-value@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" @@ -25303,6 +25599,42 @@ unique-slug@^3.0.0: dependencies: imurmurhash "^0.1.4" +unist-util-is@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-5.2.0.tgz#37eed0617b76c114fd34d44c201aa96fd928b309" + integrity sha512-Glt17jWwZeyqrFqOK0pF1Ded5U3yzJnFr8CG1GMjCWTp9zDo2p+cmD6pWbZU8AgM5WU3IzRv6+rBwhzsGh6hBQ== + +unist-util-position@^4.0.0: + version "4.0.4" + resolved "https://registry.yarnpkg.com/unist-util-position/-/unist-util-position-4.0.4.tgz#93f6d8c7d6b373d9b825844645877c127455f037" + integrity sha512-kUBE91efOWfIVBo8xzh/uZQ7p9ffYRtUbMRZBNFYwf0RK8koUMx6dGUfwylLOKmaT2cs4wSW96QoYUSXAyEtpg== + dependencies: + "@types/unist" "^2.0.0" + +unist-util-stringify-position@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-3.0.3.tgz#03ad3348210c2d930772d64b489580c13a7db39d" + integrity sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg== + dependencies: + "@types/unist" "^2.0.0" + +unist-util-visit-parents@^5.1.1: + version "5.1.3" + resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-5.1.3.tgz#b4520811b0ca34285633785045df7a8d6776cfeb" + integrity sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg== + dependencies: + "@types/unist" "^2.0.0" + unist-util-is "^5.0.0" + +unist-util-visit@^4.0.0: + version "4.1.2" + resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-4.1.2.tgz#125a42d1eb876283715a3cb5cceaa531828c72e2" + integrity sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg== + dependencies: + "@types/unist" "^2.0.0" + unist-util-is "^5.0.0" + unist-util-visit-parents "^5.1.1" + universal-user-agent@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-2.1.0.tgz#5abfbcc036a1ba490cb941f8fd68c46d3669e8e4" @@ -25468,7 +25800,7 @@ utf8@3.0.0, utf8@^3.0.0: resolved "https://registry.yarnpkg.com/utf8/-/utf8-3.0.0.tgz#f052eed1364d696e769ef058b183df88c87f69d1" integrity sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ== -util-deprecate@^1.0.1, util-deprecate@~1.0.1: +util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= @@ -25623,6 +25955,32 @@ verror@1.10.0: core-util-is "1.0.2" extsprintf "^1.2.0" +vfile-location@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/vfile-location/-/vfile-location-4.0.1.tgz#06f2b9244a3565bef91f099359486a08b10d3a95" + integrity sha512-JDxPlTbZrZCQXogGheBHjbRWjESSPEak770XwWPfw5mTc1v1nWGLB/apzZxsx8a0SJVfF8HK8ql8RD308vXRUw== + dependencies: + "@types/unist" "^2.0.0" + vfile "^5.0.0" + +vfile-message@^3.0.0: + version "3.1.3" + resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-3.1.3.tgz#1360c27a99234bebf7bddbbbca67807115e6b0dd" + integrity sha512-0yaU+rj2gKAyEk12ffdSbBfjnnj+b1zqTBv3OQCTn8yEB02bsPizwdBPrLJjHnK+cU9EMMcUnNv938XcZIkmdA== + dependencies: + "@types/unist" "^2.0.0" + unist-util-stringify-position "^3.0.0" + +vfile@^5.0.0: + version "5.3.6" + resolved "https://registry.yarnpkg.com/vfile/-/vfile-5.3.6.tgz#61b2e70690cc835a5d0d0fd135beae74e5a39546" + integrity sha512-ADBsmerdGBs2WYckrLBEmuETSPyTD4TuLxTrw0DvjirxW1ra4ZwkbzG8ndsv3Q57smvHxo677MHaQrY9yxH8cA== + dependencies: + "@types/unist" "^2.0.0" + is-buffer "^2.0.0" + unist-util-stringify-position "^3.0.0" + vfile-message "^3.0.0" + vite@^4.3.9: version "4.3.9" resolved "https://registry.yarnpkg.com/vite/-/vite-4.3.9.tgz#db896200c0b1aa13b37cdc35c9e99ee2fdd5f96d" @@ -25713,6 +26071,11 @@ wcwidth@^1.0.0, wcwidth@^1.0.1: dependencies: defaults "^1.0.3" +web-namespaces@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/web-namespaces/-/web-namespaces-2.0.1.tgz#1010ff7c650eccb2592cebeeaf9a1b253fd40692" + integrity sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ== + web3-bzz@1.10.0: version "1.10.0" resolved "https://registry.yarnpkg.com/web3-bzz/-/web3-bzz-1.10.0.tgz#ac74bc71cdf294c7080a79091079192f05c5baed" @@ -26812,3 +27175,8 @@ zustand@^4.0.0: integrity sha512-OrsfQTnRXF1LZ9/vR/IqN9ws5EXUhb149xmPjErZnUrkgxS/gAHGy2dPNIVkVvoxrVe1sIydn4JjF0dYHmGeeQ== dependencies: use-sync-external-store "1.2.0" + +zwitch@^2.0.0, zwitch@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-2.0.4.tgz#c827d4b0acb76fc3e685a4c6ec2902d51070e9d7" + integrity sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==