Skip to content

Commit

Permalink
#252 - add warning for VAAs not compatible with token bridge to be re…
Browse files Browse the repository at this point in the history
…deemed, like CCTP for instance
  • Loading branch information
sebastianscatularo committed Jul 18, 2023
1 parent 781738c commit 30ecd63
Show file tree
Hide file tree
Showing 4 changed files with 188 additions and 23 deletions.
26 changes: 17 additions & 9 deletions src/components/ChainWarningMessage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,28 @@ const useStyles = makeStyles((theme) => ({
}));

export interface ChainWarningProps {
message: WarningMessage;
message?: WarningMessage;
children?: string | JSX.Element | JSX.Element[];
}

export default function ChainWarningMessage({ message }: ChainWarningProps) {
export default function ChainWarningMessage({
children,
message,
}: ChainWarningProps) {
const classes = useStyles();
return (
<Alert variant="outlined" severity="warning" className={classes.alert}>
{message.text}
{message.link && (
<Typography component="div">
<Link href={message.link.url} target="_blank" rel="noreferrer">
{message.link.text}
</Link>
</Typography>
{children || (
<>
{message?.text}
{message?.link && (
<Typography component="div">
<Link href={message.link.url} target="_blank" rel="noreferrer">
{message.link.text}
</Link>
</Typography>
)}
</>
)}
</Alert>
);
Expand Down
55 changes: 42 additions & 13 deletions src/components/Recovery.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import {
CircularProgress,
Container,
Divider,
Link,
makeStyles,
MenuItem,
TextField,
Expand Down Expand Up @@ -119,6 +120,20 @@ import {
getEmitterAddressAndSequenceFromResponseSui,
getOriginalPackageId,
} from "@certusone/wormhole-sdk/lib/cjs/sui";
import { useVaaVerifier } from "../hooks/useVaaVerifier";
import ChainWarningMessage from "./ChainWarningMessage";

const NOT_SUPPORTED_VAA_WARNING_MESSAGE = (
<>
This VAA was not generated from Token Bridge, or the type is not supported
yet. If you have any questions or believe this is a mistake, please open a
support ticket on{" "}
<Link href="https://discord.gg/wormholecrypto" target="_blank">
Discord
</Link>
.
</>
);

const useStyles = makeStyles((theme) => ({
mainCard: {
Expand Down Expand Up @@ -538,12 +553,14 @@ export default function Recovery() {
useState<ChainId>(CHAIN_ID_SOLANA);
const { provider } = useEthereumProvider(recoverySourceChain);
const [type, setType] = useState<"Token" | "NFT">("Token");
const isNFT = type === "NFT";
const isNFT = useMemo(() => type === "NFT", [type]);
const [recoverySourceTx, setRecoverySourceTx] = useState("");
const [recoverySourceTxIsLoading, setRecoverySourceTxIsLoading] =
useState(false);
const [recoverySourceTxError, setRecoverySourceTxError] = useState("");
const [recoverySignedVAA, setRecoverySignedVAA] = useState("");
const { itIsNFTTransfer, itIsTokenBridgeTransfer } =
useVaaVerifier(recoverySignedVAA);
const [recoveryParsedVAA, setRecoveryParsedVAA] = useState<ParsedVaa | null>(
null
);
Expand Down Expand Up @@ -888,7 +905,10 @@ export default function Recovery() {
}
}, [recoverySignedVAA]);
const parsedPayloadTargetChain = parsedPayload?.targetChain;
const enableRecovery = recoverySignedVAA && parsedPayloadTargetChain;
const enableRecovery =
recoverySignedVAA &&
parsedPayloadTargetChain &&
(itIsNFTTransfer || itIsTokenBridgeTransfer);

const handleRecoverClickBase = useCallback(
(useRelayer: boolean) => {
Expand Down Expand Up @@ -997,17 +1017,26 @@ export default function Recovery() {
fullWidth
margin="normal"
/>
<RelayerRecovery
parsedPayload={parsedPayload}
signedVaa={recoverySignedVAA}
onClick={handleRecoverWithRelayerClick}
/>
<AcalaRelayerRecovery
parsedPayload={parsedPayload}
signedVaa={recoverySignedVAA}
onClick={handleRecoverWithRelayerClick}
isNFT={isNFT}
/>
{enableRecovery && (
<>
<RelayerRecovery
parsedPayload={parsedPayload}
signedVaa={recoverySignedVAA}
onClick={handleRecoverWithRelayerClick}
/>
<AcalaRelayerRecovery
parsedPayload={parsedPayload}
signedVaa={recoverySignedVAA}
onClick={handleRecoverWithRelayerClick}
isNFT={isNFT}
/>
</>
)}
{ recoverySignedVAA !== "" && !enableRecovery && (
<ChainWarningMessage>
{NOT_SUPPORTED_VAA_WARNING_MESSAGE}
</ChainWarningMessage>
)}
<ButtonWithLoader
onClick={handleRecoverClick}
disabled={!enableRecovery}
Expand Down
2 changes: 1 addition & 1 deletion src/contexts/EthereumProviderContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export const getEvmWallets = (): EVMWallet[] => {
qrModalOptions: {
explorerAllowList: [],
explorerDenyList: [],
themeMode: 'light'
themeMode: "light",
},
metadata: {
url: "https://portalbridge.com",
Expand Down
128 changes: 128 additions & 0 deletions src/hooks/useVaaVerifier.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import {
ChainId,
ParsedVaa,
hexToUint8Array,
parseNFTPayload,
parseTransferPayload,
parseVaa,
tryHexToNativeString,
} from "@certusone/wormhole-sdk";
import { useMemo } from "react";
import {
getNFTBridgeAddressForChain,
getTokenBridgeAddressForChain,
} from "../utils/consts";

enum VAAType {
UNKNOWN = 0,
TRANSFER = 1,
ATTEST = 2,
TRANSFER_WITH_PAYLOAD = 3,
}

interface ParserPayload<T> {
(buffer: Buffer): T;
}

function parsePayload<T>(
payload: Buffer,
parser: ParserPayload<T>
): T | undefined {
try {
if (payload) {
return parser(payload);
}
} catch (err) {
console.debug(`Failed to parse payload with ${parser}`, err);
}
}

function getPayloadType(payload: Buffer): VAAType {
try {
const type = payload.readInt8(0);
if (type > 0 || type <= 3) {
return type;
}
} catch (err) {
console.debug(`Failed to parse type from payload`, err);
}
return VAAType.UNKNOWN;
}

function parseVaaFromHexString(hexVaa: string): ParsedVaa {
try {
return parseVaa(hexToUint8Array(hexVaa));
} catch (err) {
console.debug(err);
}
return {} as ParsedVaa;
}

function itWasEmittedFrom(
emitterAddress: Buffer,
emitterChain: number,
getAddressFn: (chainId: ChainId) => string
): boolean {
try {
const chainId = emitterChain as ChainId;
const vaaEmitterAddress = tryHexToNativeString(
emitterAddress?.toString("hex"),
chainId
);
return (
getAddressFn(chainId)?.toUpperCase() === vaaEmitterAddress?.toUpperCase()
);
} catch (err) {
console.debug(err);
}
return false;
}

function itIsASupportedType(
type: VAAType,
...supportedTypes: VAAType[]
): boolean {
return supportedTypes.includes(type);
}

export function useVaaVerifier(hexVaa: string) {
const vaa = useMemo(() => parseVaaFromHexString(hexVaa), [hexVaa]);
const vaaEmitterChain = useMemo(() => vaa?.emitterChain as ChainId, [vaa]);
const vaaEmitterAddress = useMemo(() => vaa?.emitterAddress, [vaa]);
const nft = useMemo(() => parsePayload(vaa?.payload, parseNFTPayload), [vaa]);
const transfer = useMemo(
() => parsePayload(vaa?.payload, parseTransferPayload),
[vaa]
);
const payloadType = useMemo(() => getPayloadType(vaa?.payload), [vaa]);
const itIsTokenBridgeTransfer = useMemo(
() =>
itWasEmittedFrom(
vaaEmitterAddress,
vaaEmitterChain,
getTokenBridgeAddressForChain
) &&
itIsASupportedType(
payloadType,
VAAType.TRANSFER,
VAAType.TRANSFER_WITH_PAYLOAD
),
[vaaEmitterAddress, vaaEmitterChain, payloadType]
);
const itIsNFTTransfer = useMemo(
() =>
itWasEmittedFrom(
vaaEmitterAddress,
vaaEmitterChain,
getNFTBridgeAddressForChain
) && itIsASupportedType(payloadType, VAAType.TRANSFER),
[vaaEmitterAddress, vaaEmitterChain, payloadType]
);
return {
transfer,
nft,
payloadType,
itIsTokenBridgeTransfer,
itIsNFTTransfer,
};
}

0 comments on commit 30ecd63

Please sign in to comment.