@ -1,12 +1,8 @@
// Forked from debug script in monorepo but mostly rewritten
// https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/infra/scripts/debug-message.ts
import { BigNumber , providers } from 'ethers' ;
import { BigNumber , utils as ethersUtils , providers } from 'ethers' ;
import {
type IInterchainGasPaymaster ,
IMessageRecipient__factory ,
InterchainGasPaymaster__factory ,
} from '@hyperlane-xyz/core' ;
import { IMessageRecipient__factory , InterchainGasPaymaster__factory } from '@hyperlane-xyz/core' ;
import type { ChainMap , MultiProvider } from '@hyperlane-xyz/sdk' ;
import { utils } from '@hyperlane-xyz/utils' ;
@ -16,10 +12,10 @@ import { errorToString } from '../../utils/errors';
import { logger } from '../../utils/logger' ;
import { trimToLength } from '../../utils/string' ;
import type { ChainConfig } from '../chains/chainConfig' ;
import { getContractAddress , tryGetContractAddress } from '../chains/utils' ;
import { getContractAddress } from '../chains/utils' ;
import { isIcaMessage , tryDecodeIcaBody , tryFetchIcaAddress } from '../messages/ica' ;
import { MessageDebugDetails , MessageDebugStatus } from './types' ;
import { GasPayment , MessageDebugDetails , MessageDebugStatus } from './types' ;
type Provider = providers . Provider ;
@ -41,7 +37,6 @@ export async function debugExplorerMessage(
} = message ;
logger . debug ( ` Debugging message id: ${ msgId } ` ) ;
const originName = multiProvider . getChainName ( originDomain ) ;
const destName = multiProvider . tryGetChainName ( destDomain ) ! ;
const originProvider = multiProvider . getProvider ( originDomain ) ;
const destProvider = multiProvider . getProvider ( destDomain ) ;
@ -63,21 +58,16 @@ export async function debugExplorerMessage(
if ( deliveryResult . status && deliveryResult . details ) return deliveryResult ;
const gasEstimate = deliveryResult . gasEstimate ;
const igpAddress = tryGetContractAddress (
customChainConfigs ,
originName ,
'interchainGasPaymaster' ,
) ;
const insufficientGas = await isIgpUnderfunded (
const gasCheckResult = await tryCheckIgpGasFunded (
msgId ,
originProvider ,
igpAddress ,
gasEstimate ,
totalGasAmount ,
) ;
if ( insufficientGas ) return insufficientGas ;
if ( gasCheckResult . status && gasCheckResult . details ) return gasCheckResult ;
return noErrorFound ( ) ;
logger . debug ( ` No errors found debugging message id: ${ msgId } ` ) ;
return { . . . noErrorFound ( ) , gasDetails : gasCheckResult.gasDetails } ;
}
async function isInvalidRecipient ( provider : Provider , recipient : Address ) {
@ -152,72 +142,90 @@ async function debugMessageDelivery(
}
}
async function isIgpUnderfunded (
msgId : string ,
originProvider : Provider ,
igpAddress? : Address ,
deliveryGasEst? : string ,
totalGasAmount? : string ,
) {
if ( ! igpAddress ) {
logger . debug ( 'No IGP address provided, skipping gas funding check' ) ;
return false ;
}
const igpContract = InterchainGasPaymaster__factory . connect ( igpAddress , originProvider ) ;
const { isFunded , igpDetails } = await tryCheckIgpGasFunded (
igpContract ,
msgId ,
deliveryGasEst ,
totalGasAmount ,
) ;
if ( ! isFunded ) {
return {
status : MessageDebugStatus.GasUnderfunded ,
details : igpDetails ,
} ;
}
return false ;
}
async function tryCheckIgpGasFunded (
igp : IInterchainGasPaymaster ,
messageId : string ,
deliveryGasEst? : string ,
originProvider : Provider ,
deliveryGasEstimate? : string ,
totalGasAmount? : string ,
) {
if ( ! deliveryGasEstimate ) {
logger . warn ( 'No gas estimate provided, skipping IGP check' ) ;
return { } ;
}
try {
if ( ! deliveryGasEst ) throw new Error ( 'No gas estimate provided' ) ;
let gasAlreadyFunded = BigNumber . from ( 0 ) ;
if ( totalGasAmount ) {
const filter = igp . filters . GasPayment ( messageId , null , null ) ;
const matchedEvents = ( await igp . queryFilter ( filter ) ) || [ ] ;
logger . debug ( ` Found ${ matchedEvents . length } payments to IGP for msg ${ messageId } ` ) ;
logger . debug ( matchedEvents ) ;
for ( const payment of matchedEvents ) {
gasAlreadyFunded = gasAlreadyFunded . add ( payment . args . gasAmount ) ;
}
} else {
let gasDetails : MessageDebugDetails [ 'gasDetails' ] = {
deliveryGasEstimate ,
} ;
if ( totalGasAmount && BigNumber . from ( totalGasAmount ) . gt ( 0 ) ) {
logger . debug ( ` Using totalGasAmount info from message: ${ totalGasAmount } ` ) ;
gasAlreadyFunded = BigNumber . from ( totalGasAmount ) ;
} else {
logger . debug ( 'Querying for gas payments events for msg to any contract' ) ;
const { contractToPayments , contractToTotalGas , numPayments , numIGPs } =
await fetchGasPaymentEvents ( originProvider , messageId ) ;
gasDetails = { deliveryGasEstimate , contractToPayments } ;
logger . debug ( ` Found ${ numPayments } payments to ${ numIGPs } IGPs for msg ${ messageId } ` ) ;
if ( numIGPs === 1 ) {
gasAlreadyFunded = Object . values ( contractToTotalGas ) [ 0 ] ;
} else if ( numIGPs > 1 ) {
logger . warn ( ` >1 IGPs paid for msg ${ messageId } . Unsure which to use, skipping check. ` ) ;
return { gasDetails } ;
}
}
logger . debug ( 'Amount of gas paid for to IGP:' , gasAlreadyFunded . toString ( ) ) ;
logger . debug ( 'Amount of gas required:' , deliveryGasEst ) ;
logger . debug ( 'Approximate a mount of gas required:' , deliveryGasEstimate ) ;
if ( gasAlreadyFunded . lte ( 0 ) ) {
return { isFunded : false , igpDetails : 'Origin IGP has not received any gas payments' } ;
} else if ( gasAlreadyFunded . lte ( deliveryGasEst ) ) {
return {
isFunded : false ,
igpDetails : ` Origin IGP gas amount is ${ gasAlreadyFunded . toString ( ) } but requires ${ deliveryGasEst } ` ,
status : MessageDebugStatus.GasUnderfunded ,
details : 'Origin IGP has not received any gas payments' ,
gasDetails ,
} ;
} else if ( gasAlreadyFunded . lte ( deliveryGasEstimate ) ) {
return {
status : MessageDebugStatus.GasUnderfunded ,
details : ` Origin IGP gas amount is ${ gasAlreadyFunded . toString ( ) } but requires ${ deliveryGasEstimate } ` ,
gasDetails ,
} ;
} else {
return { isFunded : true , igpDetails : '' } ;
return { gasDetails } ;
}
} catch ( error ) {
logger . warn ( 'Error estimating delivery gas cost for message' , error ) ;
return { isFunded : true , igpDetails : '' } ;
return { } ;
}
}
async function fetchGasPaymentEvents ( provider : Provider , messageId : string ) {
const igpInterface = InterchainGasPaymaster__factory . createInterface ( ) ;
const paymentFragment = igpInterface . getEvent ( 'GasPayment' ) ;
const paymentTopics = igpInterface . encodeFilterTopics ( paymentFragment . name , [ messageId ] ) ;
const paymentLogs = ( await provider . getLogs ( { topics : paymentTopics } ) ) || [ ] ;
const contractToPayments : AddressTo < GasPayment [ ] > = { } ;
const contractToTotalGas : AddressTo < BigNumber > = { } ;
let numPayments = 0 ;
for ( const log of paymentLogs ) {
const contractAddr = log . address ;
let newEvent : ethersUtils.LogDescription ;
try {
newEvent = igpInterface . parseLog ( log ) ;
} catch ( error ) {
logger . warn ( 'Error parsing gas payment log' , error ) ;
continue ;
}
const newPayment = {
gasAmount : BigNumber.from ( newEvent . args . gasAmount ) . toString ( ) ,
paymentAmount : BigNumber.from ( newEvent . args . payment ) . toString ( ) ,
} ;
contractToPayments [ contractAddr ] = [ . . . ( contractToPayments [ contractAddr ] || [ ] ) , newPayment ] ;
contractToTotalGas [ contractAddr ] = ( contractToTotalGas [ contractAddr ] || BigNumber . from ( 0 ) ) . add (
newEvent . args . gasAmount ,
) ;
numPayments += 1 ;
}
const numIGPs = Object . keys ( contractToPayments ) . length ;
return { contractToPayments , contractToTotalGas , numPayments , numIGPs } ;
}
async function tryCheckBytecodeHandle ( provider : Provider , recipientAddress : string ) {