Skip to content

Commit

Permalink
Fixed GUI crash on invalid walletconnect request (#2476)
Browse files Browse the repository at this point in the history
* Fixed GUI crash on invalid walletconnect request

* Updated test
  • Loading branch information
ChiaMineJP authored Sep 12, 2024
1 parent b1289ae commit 85f3a2d
Show file tree
Hide file tree
Showing 5 changed files with 136 additions and 37 deletions.
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/gui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@
"redux": "4.2.1",
"regenerator-runtime": "0.14.0",
"seedrandom": "3.0.5",
"stacktrace-js": "2.0.2",
"stream-browserify": "3.0.0",
"styled-components": "6.0.7",
"unique-names-generator": "4.6.0",
Expand Down
151 changes: 116 additions & 35 deletions packages/gui/src/components/walletConnect/WalletConnectConfirmDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,55 @@
import { useGetKeysQuery } from '@chia-network/api-react';
import { ConfirmDialog, Flex, LoadingOverlay } from '@chia-network/core';
import { AlertDialog, ConfirmDialog, Flex, LoadingOverlay } from '@chia-network/core';
import { Trans } from '@lingui/macro';
import { Typography, Divider, Switch } from '@mui/material';
import React, { type ReactNode, useState, useMemo } from 'react';
import { styled } from '@mui/styles';
import React, { type ReactNode, useState, useMemo, useCallback } from 'react';
import StackTrace, { type StackFrame } from 'stacktrace-js';

import type WalletConnectCommandParam from '../../@types/WalletConnectCommandParam';
import useWalletConnectPairs from '../../hooks/useWalletConnectPairs';

import WalletConnectMetadata from './WalletConnectMetadata';

const StyledPre = styled(Typography)(() => ({
whiteSpace: 'pre-wrap',
}));

function formatStackTrace(stack: StackFrame[]) {
const stackTrace = stack.map(
({ fileName, columnNumber, lineNumber, functionName }) =>
`at ${fileName}:${lineNumber}:${columnNumber} ${functionName}`,
);
return stackTrace.join('\n');
}

type LocalErrorBoundaryProps = React.PropsWithChildren<{
fallback?: ReactNode;
onError: (error: Error, stacktrace: string) => void;
}>;
type LocalErrorBoundaryState = { error: false | Error };
class LocalErrorBoundary extends React.Component<LocalErrorBoundaryProps, LocalErrorBoundaryState> {
constructor(props: LocalErrorBoundaryProps) {
super(props);
this.state = { error: false };
}

static getDerivedStateFromError(error: Error) {
return { error };
}

async componentDidCatch(error: Error) {
this.props.onError(error, formatStackTrace(await StackTrace.fromError(error)));
}

render() {
if (this.state.error) {
return this.props.fallback;
}
return this.props.children;
}
}

export type WalletConnectConfirmDialogProps = {
topic: string;
command: string;
Expand Down Expand Up @@ -39,6 +80,7 @@ export default function WalletConnectConfirmDialog(props: WalletConnectConfirmDi
} = props;

const [values, setValues] = useState(defaultValues);
const [error, setError] = useState<{ e: Error; stacktrace: string } | false>(false);
const { getPairBySession, bypassCommand } = useWalletConnectPairs();
const { data: keys, isLoading: isLoadingPublicKeys } = useGetKeysQuery();
const key = keys?.find((item) => item.fingerprint === fingerprint);
Expand All @@ -47,21 +89,80 @@ export default function WalletConnectConfirmDialog(props: WalletConnectConfirmDi

const pair = useMemo(() => getPairBySession(topic), [topic, getPairBySession]);

function handleRememberChoiceChanged(_: any, checked: boolean) {
const handleRememberChoiceChanged = useCallback((_: any, checked: boolean) => {
setRememberChoice(checked);
}
}, []);

function handleClose(confirmed: boolean) {
if (rememberChoice) {
bypassCommand(topic, command, confirmed);
}
const onCloseConfirmDialog = useCallback(
(confirmed: boolean) => {
if (rememberChoice) {
bypassCommand(topic, command, confirmed);
}

onClose?.(confirmed);
}
onClose?.(confirmed);
},
[rememberChoice, bypassCommand, onClose, topic, command],
);

function handleChangeValues(newValues: Record<string, any>) {
setValues(newValues);
onChange?.(newValues);
const onCloseAlertDialog = useCallback(() => {
setError(false);
onClose?.(false);
}, [onClose]);

const handleChangeValues = useCallback(
(newValues: Record<string, any>) => {
setValues(newValues);
onChange?.(newValues);
},
[onChange],
);

const onError = useCallback((err: Error, stacktrace: string) => {
setError({ e: err, stacktrace });
}, []);

const paramRows = useMemo(() => {
if (params.length === 0) {
return null;
}
return (
<Flex flexDirection="column" gap={2}>
{params.map(({ label, name, hide, displayComponent }) => {
if (hide || !(name in values)) {
return null;
}

const value = values[name];
return (
<LocalErrorBoundary onError={onError}>
<Flex flexDirection="column" key={name}>
<Typography color="textPrimary">{label ?? name}</Typography>
<Typography color="textSecondary">
{displayComponent
? displayComponent(value, params, values, handleChangeValues)
: value?.toString() ?? <Trans>Not Available</Trans>}
</Typography>
</Flex>
</LocalErrorBoundary>
);
})}
</Flex>
);
}, [params, values, handleChangeValues, onError]);

if (error) {
return (
<AlertDialog title={<Trans>Invalid WalletConnect data</Trans>} onClose={onCloseAlertDialog} open={open}>
<Flex flexDirection="column" gap={2}>
<Flex flexDirection="column">
<Typography variant="h6">
<Trans>Error:</Trans> {error.e.message}
</Typography>
<StyledPre variant="body2">{error.stacktrace}</StyledPre>
</Flex>
</Flex>
</AlertDialog>
);
}

return (
Expand All @@ -70,34 +171,14 @@ export default function WalletConnectConfirmDialog(props: WalletConnectConfirmDi
confirmColor="primary"
confirmTitle={<Trans>Confirm</Trans>}
cancelTitle={<Trans>Reject</Trans>}
onClose={handleClose}
onClose={onCloseConfirmDialog}
open={open}
>
<LoadingOverlay isLoading={isLoadingPublicKeys}>
<Flex flexDirection="column" gap={2}>
<Typography variant="body1">{message}</Typography>

{params.length > 0 && (
<Flex flexDirection="column" gap={2}>
{params.map(({ label, name, hide, displayComponent }) => {
if (hide || !(name in values)) {
return null;
}

const value = values[name];
return (
<Flex flexDirection="column" key={name}>
<Typography color="textPrimary">{label ?? name}</Typography>
<Typography color="textSecondary">
{displayComponent
? displayComponent(value, params, values, handleChangeValues)
: value?.toString() ?? <Trans>Not Available</Trans>}
</Typography>
</Flex>
);
})}
</Flex>
)}
{paramRows}

<Divider />
<Flex flexDirection="column" gap={1}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,20 @@ describe('createOfferForIdsToOfferBuilderData', () => {

// call to createOfferForIdsToOfferBuilderData should throw an error
expect(() => createOfferForIdsToOfferBuilderData(walletIdsAndAmounts as any, (() => {}) as any)).toThrow(
/Invalid value for/,
/^Invalid amount /,
);
});
});
describe('when the amount is null', () => {
it('should throw an error', () => {
const walletIdsAndAmounts = {
1: null,
2: -1234,
};

// call to createOfferForIdsToOfferBuilderData should throw an error
expect(() => createOfferForIdsToOfferBuilderData(walletIdsAndAmounts as any, (() => {}) as any)).toThrow(
/^Amount is not set for /,
);
});
});
Expand Down
5 changes: 4 additions & 1 deletion packages/gui/src/util/createOfferForIdsToOfferBuilderData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ export default function createOfferForIdsToOfferBuilderData(
const numericValue = new BigNumber(amount);

if (numericValue.isNaN()) {
throw new Error(`Invalid value for ${walletOrAssetId}: ${amount}`);
if (amount === null) {
throw new Error(`Amount is not set for walletId(assetId):${walletOrAssetId}`);
}
throw new Error(`Invalid amount '${amount}' for walletId(assetId):${walletOrAssetId}`);
}

const section = numericValue.isPositive() ? offerBuilderData.requested : offerBuilderData.offered;
Expand Down

0 comments on commit 85f3a2d

Please sign in to comment.