diff --git a/src/consts/links.ts b/src/consts/links.ts index 44478e0..f3a03a5 100644 --- a/src/consts/links.ts +++ b/src/consts/links.ts @@ -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', }; diff --git a/src/features/debugger/debugMessage.ts b/src/features/debugger/debugMessage.ts index 1fcc39d..dd85d30 100644 --- a/src/features/debugger/debugMessage.ts +++ b/src/features/debugger/debugMessage.ts @@ -43,6 +43,8 @@ export async function debugMessage( }: Message, ): Promise { 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 = {}; + 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, }; } } diff --git a/src/features/debugger/types.ts b/src/features/debugger/types.ts index 6120e29..85ef133 100644 --- a/src/features/debugger/types.ts +++ b/src/features/debugger/types.ts @@ -19,6 +19,10 @@ export interface MessageDebugResult { ismAddress: Address; moduleType: IsmModuleTypes; }; + calldataDetails?: { + handleCalldata: HexString; + contract: Address; + }; } export interface GasPayment { diff --git a/src/features/messages/cards/GasDetailsCard.tsx b/src/features/messages/cards/GasDetailsCard.tsx index 7d1bf0a..e5b910a 100644 --- a/src/features/messages/cards/GasDetailsCard.tsx +++ b/src/features/messages/cards/GasDetailsCard.tsx @@ -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' }, - { value: 'gwei', display: 'Gwei' }, - { value: 'wei', display: 'Wei' }, -]; - 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]); + const [unit, setUnit] = useState(unitOptions[0].value); const { totalGasAmount, paymentFormatted, numPayments, avgPrice, paymentsWithAddr } = @@ -76,7 +83,7 @@ export function GasDetailsCard({ message, blur, igpPayments = {} }: Props) {

Interchain gas payments are required to fund message delivery on the destination chain.{' '} -

Delivery to destination chain is currently failing
- {debugResult && ( - <> -
- {debugStatusToDesc[debugResult.status]} -
-
- {debugResult.description} -
- +
{`Delivery to destination chain seems to be failing ${ + debugResult ? ': ' + debugStatusToDesc[debugResult.status] : '' + }`}
+ {!!debugResult?.description && ( +
+ {debugResult.description} +
)} + ); } else if (status === MessageStatus.Pending) { @@ -82,6 +83,7 @@ export function DestinationTransactionCard({ )} + ); } else { @@ -114,7 +116,7 @@ function TransactionCard({ children, }: PropsWithChildren<{ chainId: ChainId; title: string; helpText: string }>) { return ( - +
@@ -183,7 +185,7 @@ function TransactionDetails({ /> {txExplorerLink && ( ) { return ( -
-
{children}
+
+
{children}
); } +function CallDataModal({ debugResult }: { debugResult?: MessageDebugResult }) { + const [isOpen, setIsOpen] = useState(false); + if (!debugResult?.calldataDetails) return null; + const { contract, handleCalldata } = debugResult.calldataDetails; + return ( + <> + + setIsOpen(false)} + maxWidth="max-w-sm sm:max-w-md" + > +
+ + + ); +} + 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', +}; diff --git a/src/global.d.ts b/src/global.d.ts index 97748aa..a0cc38e 100644 --- a/src/global.d.ts +++ b/src/global.d.ts @@ -1,4 +1,5 @@ declare type Address = string; +declare type HexString = string; declare type ChainId = number; declare type DomainId = number; declare type AddressTo = Record; diff --git a/tailwind.config.js b/tailwind.config.js index 85aadf4..06ffdd6 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -11,6 +11,7 @@ module.exports = { mono: ['Courier New', 'monospace'], }, screens: { + any: '1px', xs: '480px', ...defaultTheme.screens, },