Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 56 additions & 14 deletions src/features/debugger/debugMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ import { IRegistry } from '@hyperlane-xyz/registry';
import {
ChainMap,
ChainMetadata,
isProxy,
MAILBOX_VERSION,
MultiProtocolProvider,
isProxy,
proxyImplementation,
} from '@hyperlane-xyz/sdk';
import {
Expand All @@ -30,7 +30,7 @@ import {
import { Message } from '../../types';
import { logger } from '../../utils/logger';
import { getMailboxAddress } from '../chains/utils';
import { isIcaMessage, tryDecodeIcaBody, tryFetchIcaAddress } from '../messages/ica';
import { computeIcaAddress, decodeIcaBody, IcaMessageType, isIcaMessage } from '../messages/ica';

import { debugIgnoredChains } from '../../consts/config';
import { GasPayment, IsmModuleTypes, MessageDebugResult, MessageDebugStatus } from './types';
Expand Down Expand Up @@ -207,12 +207,19 @@ async function debugMessageDelivery(
};
}

const icaCallErr = await tryDebugIcaMsg(sender, recipient, body, originDomain, destProvider);
if (icaCallErr) {
const icaDebugResult = await tryDebugIcaMsg(
sender,
recipient,
body,
originDomain,
destProvider,
);
if (icaDebugResult) {
return {
status: MessageDebugStatus.IcaCallFailure,
description: icaCallErr,
description: `ICA call ${icaDebugResult.failedCallIndex + 1} of ${icaDebugResult.totalCalls} cannot be executed. ${icaDebugResult.errorReason}`,
calldataDetails,
icaDetails: icaDebugResult,
};
}

Expand Down Expand Up @@ -389,35 +396,68 @@ async function tryCheckBytecodeHandle(provider: Provider, recipientAddress: stri
}
}

interface IcaDebugResult {
failedCallIndex: number;
totalCalls: number;
errorReason: string;
}

async function tryDebugIcaMsg(
sender: Address,
recipient: Address,
body: string,
originDomainId: DomainId,
destinationProvider: Provider,
) {
): Promise<IcaDebugResult | null> {
if (!isIcaMessage({ sender, recipient })) return null;
logger.debug('Message is for an ICA');

const decodedBody = tryDecodeIcaBody(body);
const decodedBody = decodeIcaBody(body);
if (!decodedBody) return null;

const { sender: originalSender, calls } = decodedBody;
// Only debug CALLS type messages - COMMITMENT and REVEAL have different flows
if (decodedBody.messageType !== IcaMessageType.CALLS) {
logger.debug('Skipping ICA debug for non-CALLS message type');
return null;
}

const icaAddress = await tryFetchIcaAddress(originDomainId, originalSender, destinationProvider);
if (!icaAddress) return null;
const { calls, owner, ism, salt } = decodedBody;

// Compute the actual ICA address for accurate gas estimation
// sender is the origin ICA router, recipient is the destination ICA router
const icaAddress = await computeIcaAddress(
originDomainId,
owner!, // owner is defined for CALLS type
sender, // origin router (sender of ICA message)
recipient, // destination router
ism,
salt,
destinationProvider,
);

if (!icaAddress) {
logger.debug('Could not compute ICA address, skipping call checks');
return null;
}

logger.debug(`Computed ICA address: ${icaAddress}`);

for (let i = 0; i < calls.length; i++) {
const call = calls[i];
logger.debug(`Checking ica call ${i + 1} of ${calls.length}`);
logger.debug(`Checking ICA call ${i + 1} of ${calls.length}`);
const errorReason = await tryCheckIcaCall(
icaAddress,
call.destinationAddress,
call.callBytes,
call.to,
call.data,
call.value,
destinationProvider,
);
if (errorReason) {
return `ICA call ${i + 1} of ${calls.length} cannot be executed. ${errorReason}`;
return {
failedCallIndex: i,
totalCalls: calls.length,
errorReason,
};
}
}

Expand All @@ -428,13 +468,15 @@ async function tryCheckIcaCall(
icaAddress: string,
destinationAddress: string,
callBytes: string,
callValue: string,
destinationProvider: Provider,
) {
try {
await destinationProvider.estimateGas({
to: destinationAddress,
data: callBytes,
from: icaAddress,
value: BigNumber.from(callValue),
});
logger.debug(`No call error found for call from ${icaAddress} to ${destinationAddress}`);
return null;
Expand Down
5 changes: 5 additions & 0 deletions src/features/debugger/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ export interface MessageDebugResult {
contract: Address;
mailbox: Address;
};
icaDetails?: {
failedCallIndex: number; // 0-based index of the failed call
totalCalls: number;
errorReason: string;
};
}

export interface GasPayment {
Expand Down
4 changes: 2 additions & 2 deletions src/features/messages/MessageDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export function MessageDetails({ messageId, message: messageFromUrlParams }: Pro
const isFetching = isGraphQlFetching || isPiFetching;
const isError = isGraphQlError || isPiError;
const blur = !isMessageFound;
const isIcaMsg = useIsIcaMessage(_message);
const isIcaMsg = useIsIcaMessage({ sender: _message.sender, recipient: _message.recipient });

// If message isn't delivered, attempt to check for
// more recent updates and possibly debug info
Expand Down Expand Up @@ -138,6 +138,7 @@ export function MessageDetails({ messageId, message: messageFromUrlParams }: Pro
warpRouteDetails={warpRouteDetails}
blur={blur}
/>
{isIcaMsg && <IcaDetailsCard message={message} blur={blur} debugResult={debugResult} />}
<ContentDetailsCard message={message} blur={blur} />
<GasDetailsCard
message={message}
Expand All @@ -147,7 +148,6 @@ export function MessageDetails({ messageId, message: messageFromUrlParams }: Pro
{debugResult?.ismDetails && (
<IsmDetailsCard ismDetails={debugResult.ismDetails} blur={blur} />
)}
{isIcaMsg && <IcaDetailsCard message={message} blur={blur} />}
</div>
</>
);
Expand Down
Loading