Merge pull request #42 from hyperlane-xyz/show-calldata

Surface Handle fn calldata
pull/44/head
J M Rossy 2 years ago committed by GitHub
commit 6b2e934dc0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      src/consts/links.ts
  2. 36
      src/features/debugger/debugMessage.ts
  3. 4
      src/features/debugger/types.ts
  4. 17
      src/features/messages/cards/GasDetailsCard.tsx
  5. 68
      src/features/messages/cards/TransactionCard.tsx
  6. 1
      src/global.d.ts
  7. 1
      tailwind.config.js

@ -7,4 +7,6 @@ export const links = {
jobs: 'https://jobs.lever.co/Hyperlane',
twitter: 'https://twitter.com/hyperlane_xyz',
blog: 'https://medium.com/hyperlane',
tenderlySimDocs:
'https://docs.tenderly.co/simulations-and-forks/how-to-simulate-a-transaction/using-simulation-ui',
};

@ -43,6 +43,8 @@ export async function debugMessage(
}: Message,
): Promise<MessageDebugResult> {
logger.debug(`Debugging message id: ${msgId}`);
// Prepare some useful data/encodings
const messageBytes = utils.formatMessage(
MAILBOX_VERSION,
nonce,
@ -52,12 +54,14 @@ export async function debugMessage(
recipient,
body,
);
const destName = multiProvider.tryGetChainName(destDomain)!;
const originProvider = multiProvider.getProvider(originDomain);
const destProvider = multiProvider.getProvider(destDomain);
const senderBytes = utils.addressToBytes32(sender);
// Create a bag to hold all the useful info collected along the way
const details: Omit<MessageDebugResult, 'status' | 'description'> = {};
const recipInvalid = await isInvalidRecipient(destProvider, recipient);
if (recipInvalid) return recipInvalid;
@ -72,7 +76,7 @@ export async function debugMessage(
body,
);
if (deliveryResult.status && deliveryResult.description) return deliveryResult;
const gasEstimate = deliveryResult.gasEstimate;
else details.calldataDetails = deliveryResult.calldataDetails;
const ismCheckResult = await checkMultisigIsmEmpty(
recipient,
@ -80,24 +84,23 @@ export async function debugMessage(
destMailbox,
destProvider,
);
if (ismCheckResult.status && ismCheckResult.description) return ismCheckResult;
const ismDetails = ismCheckResult.ismDetails;
if (ismCheckResult.status && ismCheckResult.description) return { ...ismCheckResult, ...details };
else details.ismDetails = ismCheckResult.ismDetails;
const gasCheckResult = await tryCheckIgpGasFunded(
msgId,
originProvider,
gasEstimate,
deliveryResult.gasEstimate,
totalGasAmount,
);
if (gasCheckResult?.status && gasCheckResult?.description)
return { ...gasCheckResult, ismDetails };
const gasDetails = gasCheckResult?.gasDetails;
return { ...gasCheckResult, ...details };
else details.gasDetails = gasCheckResult?.gasDetails;
logger.debug(`No errors found debugging message id: ${msgId}`);
return {
...noErrorFound(),
gasDetails,
ismDetails,
...details,
};
}
@ -128,6 +131,12 @@ async function debugMessageDelivery(
body: string,
) {
const recipientContract = IMessageRecipient__factory.connect(recipient, destProvider);
const handleCalldata = recipientContract.interface.encodeFunctionData('handle', [
originDomain,
senderBytes,
body,
]);
const calldataDetails = { handleCalldata, contract: recipient };
try {
// TODO add special case for Arbitrum:
// TODO account for mailbox handling gas overhead
@ -136,14 +145,12 @@ async function debugMessageDelivery(
originDomain,
senderBytes,
body,
{
from: destMailbox,
},
{ from: destMailbox },
);
logger.debug(
`Calling recipient handle function from the inbox does not revert. Gas: ${deliveryGasEst.toString()}`,
);
return { gasEstimate: deliveryGasEst.toString() };
return { gasEstimate: deliveryGasEst.toString(), calldataDetails };
} catch (err: any) {
logger.info('Estimate gas call failed:', err);
const errorReason = extractReasonString(err);
@ -155,6 +162,7 @@ async function debugMessageDelivery(
return {
status: MessageDebugStatus.RecipientNotHandler,
description: `Recipient contract should have handle function of signature: ${HANDLE_FUNCTION_SIG}. Check that recipient is not a proxy. Error: ${errorReason}`,
calldataDetails,
};
}
@ -163,12 +171,14 @@ async function debugMessageDelivery(
return {
status: MessageDebugStatus.IcaCallFailure,
description: icaCallErr,
calldataDetails,
};
}
return {
status: MessageDebugStatus.HandleCallFailure,
description: errorReason,
calldataDetails,
};
}
}

@ -19,6 +19,10 @@ export interface MessageDebugResult {
ismAddress: Address;
moduleType: IsmModuleTypes;
};
calldataDetails?: {
handleCalldata: HexString;
contract: Address;
};
}
export interface GasPayment {

@ -11,7 +11,9 @@ import FuelPump from '../../../images/icons/fuel-pump.svg';
import { Message } from '../../../types';
import { BigNumberMax, fromWei } from '../../../utils/amount';
import { logger } from '../../../utils/logger';
import { toTitleCase } from '../../../utils/string';
import { GasPayment } from '../../debugger/types';
import { useMultiProvider } from '../../providers/multiProvider';
import { KeyValueRow } from './KeyValueRow';
@ -21,13 +23,18 @@ interface Props {
blur: boolean;
}
const unitOptions = [
{ value: 'ether', display: 'Eth' },
export function GasDetailsCard({ message, blur, igpPayments = {} }: Props) {
const multiProvider = useMultiProvider();
const unitOptions = useMemo(() => {
const originMetadata = multiProvider.tryGetChainMetadata(message.originChainId);
const nativeCurrencyName = originMetadata?.nativeToken?.symbol || 'Eth';
return [
{ value: 'ether', display: toTitleCase(nativeCurrencyName) },
{ value: 'gwei', display: 'Gwei' },
{ value: 'wei', display: 'Wei' },
];
];
}, [message, multiProvider]);
export function GasDetailsCard({ message, blur, igpPayments = {} }: Props) {
const [unit, setUnit] = useState(unitOptions[0].value);
const { totalGasAmount, paymentFormatted, numPayments, avgPrice, paymentsWithAddr } =
@ -76,7 +83,7 @@ export function GasDetailsCard({ message, blur, igpPayments = {} }: Props) {
<p className="text-sm">
Interchain gas payments are required to fund message delivery on the destination chain.{' '}
<a
href={`${links.docs}/docs/build-with-hyperlane/guides/paying-for-interchain-gas`}
href={`${links.docs}/docs/protocol/interchain-gas-payments`}
target="_blank"
rel="noopener noreferrer"
className="cursor-pointer text-blue-500 hover:text-blue-400 active:text-blue-300 transition-all"

@ -1,9 +1,11 @@
import { PropsWithChildren, ReactNode } from 'react';
import { PropsWithChildren, ReactNode, useState } from 'react';
import { Spinner } from '../../../components/animation/Spinner';
import { ChainLogo } from '../../../components/icons/ChainLogo';
import { HelpIcon } from '../../../components/icons/HelpIcon';
import { Card } from '../../../components/layout/Card';
import { Modal } from '../../../components/layout/Modal';
import { links } from '../../../consts/links';
import { MessageStatus, MessageTx } from '../../../types';
import { getDateTimeString, getHumanReadableTimeString } from '../../../utils/time';
import { getChainDisplayName } from '../../chains/utils';
@ -11,6 +13,7 @@ import { debugStatusToDesc } from '../../debugger/strings';
import { MessageDebugResult } from '../../debugger/types';
import { useMultiProvider } from '../../providers/multiProvider';
import { LabelAndCodeBlock } from './CodeBlock';
import { KeyValueRow } from './KeyValueRow';
export function OriginTransactionCard({
@ -59,17 +62,15 @@ export function DestinationTransactionCard({
} else if (status === MessageStatus.Failing) {
content = (
<DeliveryStatus>
<div className="text-gray-700">Delivery to destination chain is currently failing</div>
{debugResult && (
<>
<div className="mt-4 text-gray-700 text-center">
{debugStatusToDesc[debugResult.status]}
</div>
<div className="mt-4 text-gray-700 text-sm max-w-sm text-center break-words">
<div className="text-sm text-gray-800 leading-relaxed">{`Delivery to destination chain seems to be failing ${
debugResult ? ': ' + debugStatusToDesc[debugResult.status] : ''
}`}</div>
{!!debugResult?.description && (
<div className="mt-5 text-sm text-gray-800 text-center leading-relaxed break-words">
{debugResult.description}
</div>
</>
)}
<CallDataModal debugResult={debugResult} />
</DeliveryStatus>
);
} else if (status === MessageStatus.Pending) {
@ -82,6 +83,7 @@ export function DestinationTransactionCard({
</div>
)}
<Spinner classes="mt-4 scale-75" />
<CallDataModal debugResult={debugResult} />
</DeliveryStatus>
);
} else {
@ -114,7 +116,7 @@ function TransactionCard({
children,
}: PropsWithChildren<{ chainId: ChainId; title: string; helpText: string }>) {
return (
<Card classes="flex-1 min-w-fit space-y-3">
<Card classes="flex flex-col flex-1 min-w-fit space-y-3">
<div className="flex items-center justify-between">
<div className="relative -top-px -left-0.5">
<ChainLogo chainId={chainId} />
@ -183,7 +185,7 @@ function TransactionDetails({
/>
{txExplorerLink && (
<a
className="block text-sm text-gray-500 pl-px underline"
className={`block ${styles.textLink}`}
href={txExplorerLink}
target="_blank"
rel="noopener noreferrer"
@ -197,14 +199,54 @@ function TransactionDetails({
function DeliveryStatus({ children }: PropsWithChildren<unknown>) {
return (
<div className="py-5 flex flex-col items-center text-gray-500 text-center">
<div className="max-w-xs">{children}</div>
<div className="pb-2 flex-1 flex flex-col items-center justify-center text-gray-500 text-center">
<div className="max-w-sm">{children}</div>
</div>
);
}
function CallDataModal({ debugResult }: { debugResult?: MessageDebugResult }) {
const [isOpen, setIsOpen] = useState(false);
if (!debugResult?.calldataDetails) return null;
const { contract, handleCalldata } = debugResult.calldataDetails;
return (
<>
<button onClick={() => setIsOpen(true)} className={`mt-5 ${styles.textLink}`}>
View calldata details
</button>
<Modal
isOpen={isOpen}
title="Message Delivery Calldata"
close={() => setIsOpen(false)}
maxWidth="max-w-sm sm:max-w-md"
>
<div className="mt-2 flex flex-col space-y-3.5">
<p className="text-sm">
{`The last step of message delivery is the recipient contract's 'handle' function. If the handle reverting, try debugging it with `}
<a
className={`${styles.textLink} any:text-blue-500`}
href={links.tenderlySimDocs}
target="_blank"
rel="noopener noreferrer"
>
Tenderly.
</a>
</p>
<LabelAndCodeBlock label="Recipient contract address:" value={contract} />
<LabelAndCodeBlock label="Handle function input calldata:" value={handleCalldata} />
</div>
</Modal>
</>
);
}
const helpText = {
origin: 'Info about the transaction that initiated the message placement into the outbox.',
destination:
'Info about the transaction that triggered the delivery of the message from an inbox.',
};
const styles = {
textLink:
'text-sm text-gray-500 hover:text-gray-600 active:text-gray-700 underline underline-offset-1 transition-all',
};

1
src/global.d.ts vendored

@ -1,4 +1,5 @@
declare type Address = string;
declare type HexString = string;
declare type ChainId = number;
declare type DomainId = number;
declare type AddressTo<T> = Record<Address, T>;

@ -11,6 +11,7 @@ module.exports = {
mono: ['Courier New', 'monospace'],
},
screens: {
any: '1px',
xs: '480px',
...defaultTheme.screens,
},

Loading…
Cancel
Save