diff --git a/.changeset/warm-foxes-jam.md b/.changeset/warm-foxes-jam.md new file mode 100644 index 000000000..956235f2d --- /dev/null +++ b/.changeset/warm-foxes-jam.md @@ -0,0 +1,5 @@ +--- +'@hyperlane-xyz/sdk': minor +--- + +Supprt passing foreignDeployments to HypERC20App constructor diff --git a/.registryrc b/.registryrc index 72e1774b8..7c32cf127 100644 --- a/.registryrc +++ b/.registryrc @@ -1 +1 @@ -4c4fadfba88b5ad1c310941eb282ae1fc07aa939 +29f4901c1d1a63944b0f5f2ee50f499568260004 diff --git a/typescript/infra/config/environments/mainnet3/warp/configGetters/getArbitrumNeutronEclipWarpConfig.ts b/typescript/infra/config/environments/mainnet3/warp/configGetters/getArbitrumNeutronEclipWarpConfig.ts new file mode 100644 index 000000000..ca91f805e --- /dev/null +++ b/typescript/infra/config/environments/mainnet3/warp/configGetters/getArbitrumNeutronEclipWarpConfig.ts @@ -0,0 +1,34 @@ +import { + ChainMap, + RouterConfig, + TokenRouterConfig, + TokenType, +} from '@hyperlane-xyz/sdk'; + +export const getArbitrumNeutronEclipWarpConfig = async ( + routerConfig: ChainMap, +): Promise> => { + const neutronRouter = + '6b04c49fcfd98bc4ea9c05cd5790462a39537c00028333474aebe6ddf20b73a3'; + + // @ts-ignore - foreignDeployment configs dont conform to the TokenRouterConfig + const neutron: TokenRouterConfig = { + foreignDeployment: neutronRouter, + }; + + const arbitrum: TokenRouterConfig = { + ...routerConfig.arbitrum, + type: TokenType.synthetic, + name: 'Eclipse Fi', + symbol: 'ECLIP', + decimals: 6, + totalSupply: 0, + gas: 600_000, + interchainSecurityModule: '0x53a5c239d62ff35c98e0ec9612c86517748fff59', // TODO: we should replace this with an ISM config + }; + + return { + neutron, + arbitrum, + }; +}; diff --git a/typescript/infra/config/environments/mainnet3/warp/configGetters/getArbitrumNeutronTiaWarpConfig.ts b/typescript/infra/config/environments/mainnet3/warp/configGetters/getArbitrumNeutronTiaWarpConfig.ts new file mode 100644 index 000000000..37f194a48 --- /dev/null +++ b/typescript/infra/config/environments/mainnet3/warp/configGetters/getArbitrumNeutronTiaWarpConfig.ts @@ -0,0 +1,46 @@ +import { + ChainMap, + IsmType, + RouterConfig, + TokenRouterConfig, + TokenType, +} from '@hyperlane-xyz/sdk'; + +export const getArbitrumNeutronTiaWarpConfig = async ( + routerConfig: ChainMap, +): Promise> => { + const neutronRouter = + '910926c4cf95d107237a9cf0b3305fe9c81351ebcba3d218ceb0e4935d92ceac'; + + // @ts-ignore - foreignDeployment configs dont conform to the TokenRouterConfig + const neutron: TokenRouterConfig = { + foreignDeployment: neutronRouter, + }; + + const arbitrum: TokenRouterConfig = { + ...routerConfig.arbitrum, + interchainSecurityModule: { + type: IsmType.MESSAGE_ID_MULTISIG, + validators: [ + '0xa9b8c1f4998f781f958c63cfcd1708d02f004ff0', + '0xb65438a014fb05fbadcfe35bc6e25d372b6ba460', + '0xc79503a3e3011535a9c60f6d21f76f59823a38bd', + '0x42fa752defe92459370a052b6387a87f7de9b80c', + '0x54b2cca5091b098a1a993dec03c4d1ee9af65999', + '0x47aa126e05933b95c5eb90b26e6b668d84f4b25a', + ], + threshold: 4, + }, + type: TokenType.synthetic, + name: 'TIA', + symbol: 'TIA.n', + decimals: 6, + totalSupply: 0, + gas: 600_000, + }; + + return { + arbitrum, + neutron, + }; +}; diff --git a/typescript/infra/config/environments/mainnet3/warp/configGetters/getInevmInjectiveINJWarpConfig.ts b/typescript/infra/config/environments/mainnet3/warp/configGetters/getInevmInjectiveINJWarpConfig.ts new file mode 100644 index 000000000..438fe23e9 --- /dev/null +++ b/typescript/infra/config/environments/mainnet3/warp/configGetters/getInevmInjectiveINJWarpConfig.ts @@ -0,0 +1,28 @@ +import { + ChainMap, + RouterConfig, + TokenRouterConfig, + TokenType, +} from '@hyperlane-xyz/sdk'; + +export const getInevmInjectiveINJWarpConfig = async ( + routerConfig: ChainMap, +): Promise> => { + const injectiveRouter = 'inj1mv9tjvkaw7x8w8y9vds8pkfq46g2vcfkjehc6k'; + + // @ts-ignore - foreignDeployment configs don't conform to the TokenRouterConfig + const injective: TokenRouterConfig = { + type: TokenType.native, + foreignDeployment: injectiveRouter, + }; + + const inevm: TokenRouterConfig = { + ...routerConfig.inevm, + type: TokenType.native, + }; + + return { + injective, + inevm, + }; +}; diff --git a/typescript/infra/config/environments/mainnet3/warp/configGetters/getMantapacificNeutronTiaWarpConfig.ts b/typescript/infra/config/environments/mainnet3/warp/configGetters/getMantapacificNeutronTiaWarpConfig.ts new file mode 100644 index 000000000..d606e9f48 --- /dev/null +++ b/typescript/infra/config/environments/mainnet3/warp/configGetters/getMantapacificNeutronTiaWarpConfig.ts @@ -0,0 +1,46 @@ +import { + ChainMap, + IsmType, + RouterConfig, + TokenRouterConfig, + TokenType, +} from '@hyperlane-xyz/sdk'; + +export const getMantapacificNeutronTiaWarpConfig = async ( + routerConfig: ChainMap, +): Promise> => { + const neutronRouter = + '0xc5fc6899019cb4a7649981d89eb7b1a0929d0a85b2d41802f3315129ad4b581a'; + + // @ts-ignore - foreignDeployment configs don't conform to the TokenRouterConfig + const neutron: TokenRouterConfig = { + foreignDeployment: neutronRouter, + }; + + const mantapacific: TokenRouterConfig = { + ...routerConfig.mantapacific, + interchainSecurityModule: { + type: IsmType.MESSAGE_ID_MULTISIG, + validators: [ + '0xa9b8c1f4998f781f958c63cfcd1708d02f004ff0', + '0xb65438a014fb05fbadcfe35bc6e25d372b6ba460', + '0xc79503a3e3011535a9c60f6d21f76f59823a38bd', + '0x42fa752defe92459370a052b6387a87f7de9b80c', + '0x54b2cca5091b098a1a993dec03c4d1ee9af65999', + '0x47aa126e05933b95c5eb90b26e6b668d84f4b25a', + ], + threshold: 4, + }, + type: TokenType.synthetic, + name: 'TIA', + symbol: 'TIA', + decimals: 6, + totalSupply: 0, + gas: 600_000, + }; + + return { + mantapacific, + neutron, + }; +}; diff --git a/typescript/infra/config/warp.ts b/typescript/infra/config/warp.ts index e6bfd4b49..2a638da21 100644 --- a/typescript/infra/config/warp.ts +++ b/typescript/infra/config/warp.ts @@ -9,11 +9,15 @@ import { getHyperlaneCore } from '../scripts/core-utils.js'; import { EnvironmentConfig } from '../src/config/environment.js'; import { getAncient8EthereumUSDCWarpConfig } from './environments/mainnet3/warp/configGetters/getAncient8EthereumUSDCWarpConfig.js'; +import { getArbitrumNeutronEclipWarpConfig } from './environments/mainnet3/warp/configGetters/getArbitrumNeutronEclipWarpConfig.js'; +import { getArbitrumNeutronTiaWarpConfig } from './environments/mainnet3/warp/configGetters/getArbitrumNeutronTiaWarpConfig.js'; import { getEthereumInevmUSDCWarpConfig } from './environments/mainnet3/warp/configGetters/getEthereumInevmUSDCWarpConfig.js'; import { getEthereumInevmUSDTWarpConfig } from './environments/mainnet3/warp/configGetters/getEthereumInevmUSDTWarpConfig.js'; import { getEthereumVictionETHWarpConfig } from './environments/mainnet3/warp/configGetters/getEthereumVictionETHWarpConfig.js'; import { getEthereumVictionUSDCWarpConfig } from './environments/mainnet3/warp/configGetters/getEthereumVictionUSDCWarpConfig.js'; import { getEthereumVictionUSDTWarpConfig } from './environments/mainnet3/warp/configGetters/getEthereumVictionUSDTWarpConfig.js'; +import { getInevmInjectiveINJWarpConfig } from './environments/mainnet3/warp/configGetters/getInevmInjectiveINJWarpConfig.js'; +import { getMantapacificNeutronTiaWarpConfig } from './environments/mainnet3/warp/configGetters/getMantapacificNeutronTiaWarpConfig.js'; export enum WarpRouteIds { Ancient8EthereumUSDC = 'USDC/ancient8-ethereum', @@ -36,14 +40,15 @@ export const warpConfigGetterMap: Record< [WarpRouteIds.Ancient8EthereumUSDC]: getAncient8EthereumUSDCWarpConfig, [WarpRouteIds.EthereumInevmUSDC]: getEthereumInevmUSDCWarpConfig, [WarpRouteIds.EthereumInevmUSDT]: getEthereumInevmUSDTWarpConfig, - // [WarpRouteIds.ArbitrumNeutronEclip]: getArbitrumNeutronEclipWarpConfig, // TODO - // [WarpRouteIds.ArbitrumNeutronTIA]: getArbitrumNeutronTiaWarpConfig, // TODO - // [WarpRouteIds.ArbitrumBaseBlastBscEthereumFraxtalLineaModeOptimismEZETH]: getArbitrumBaseBlastBscEthereumFraxtalLineaModeOptimismEZETHWarpConfig, // TODO - // [WarpRouteIds.InevmInjectiveINJ]: getInevmInjectiveINJWarpConfig, // TODO + [WarpRouteIds.ArbitrumNeutronEclip]: getArbitrumNeutronEclipWarpConfig, + [WarpRouteIds.ArbitrumNeutronTIA]: getArbitrumNeutronTiaWarpConfig, + // [WarpRouteIds.ArbitrumBaseBlastBscEthereumFraxtalLineaModeOptimismEZETH]: + // getRenzoEZETHWarpConfig, // TODO + [WarpRouteIds.InevmInjectiveINJ]: getInevmInjectiveINJWarpConfig, [WarpRouteIds.EthereumVictionETH]: getEthereumVictionETHWarpConfig, [WarpRouteIds.EthereumVictionUSDC]: getEthereumVictionUSDCWarpConfig, [WarpRouteIds.EthereumVictionUSDT]: getEthereumVictionUSDTWarpConfig, - // [WarpRouteIds.MantapacificNeutronTIA]: getEthereumVictionUSDTWarpConfig, // TODO + [WarpRouteIds.MantapacificNeutronTIA]: getMantapacificNeutronTiaWarpConfig, }; export async function getWarpConfig( diff --git a/typescript/infra/scripts/agent-utils.ts b/typescript/infra/scripts/agent-utils.ts index 1cd04606d..5591be685 100644 --- a/typescript/infra/scripts/agent-utils.ts +++ b/typescript/infra/scripts/agent-utils.ts @@ -448,10 +448,7 @@ export function getAddresses(environment: DeployEnvironment, module: Modules) { } } -export function getWarpAddresses( - environment: DeployEnvironment, - warpRouteId: string, -) { +export function getWarpAddresses(warpRouteId: string) { const registry = getRegistry(); const warpRouteConfig = registry.getWarpRoute(warpRouteId); diff --git a/typescript/infra/scripts/check-deploy.ts b/typescript/infra/scripts/check-deploy.ts index f7ee03b0e..7c425eb77 100644 --- a/typescript/infra/scripts/check-deploy.ts +++ b/typescript/infra/scripts/check-deploy.ts @@ -2,8 +2,6 @@ import { HelloWorldChecker } from '@hyperlane-xyz/helloworld'; import { HypERC20App, HypERC20Checker, - HypERC20Factories, - HyperlaneAddressesMap, HyperlaneCoreChecker, HyperlaneIgp, HyperlaneIgpChecker, @@ -13,6 +11,8 @@ import { InterchainAccountConfig, InterchainQuery, InterchainQueryChecker, + attachContractsMapAndGetForeignDeployments, + hypERC20factories, } from '@hyperlane-xyz/sdk'; import { objFilter } from '@hyperlane-xyz/utils'; @@ -153,16 +153,39 @@ async function check() { throw new Error('Warp route id required for warp module'); } const config = await getWarpConfig(multiProvider, envConfig, warpRouteId); - const addresses = getWarpAddresses(environment, warpRouteId); + const addresses = getWarpAddresses(warpRouteId); const filteredAddresses = Object.keys(addresses) // filter out changes not in config .filter((key) => key in config) .reduce((obj, key) => { obj[key] = addresses[key]; return obj; }, {} as typeof addresses); - const app = HypERC20App.fromAddressesMap( - filteredAddresses as HyperlaneAddressesMap, + + const { contractsMap, foreignDeployments } = + attachContractsMapAndGetForeignDeployments( + filteredAddresses, + hypERC20factories, + multiProvider, + ); + + // log error and return is foreign deployment chain is specifically checked + if ( + (chain && foreignDeployments[chain]) || + (fork && foreignDeployments[fork]) + ) { + console.log( + `${ + chain ?? fork + } is non evm and it not compatible with warp checker tooling`, + ); + return; + } + + const app = new HypERC20App( + contractsMap, multiProvider, + undefined, + foreignDeployments, ); const checker = new HypERC20Checker( diff --git a/typescript/infra/src/utils/violation.ts b/typescript/infra/src/utils/violation.ts index 24c6d0f57..f73502b52 100644 --- a/typescript/infra/src/utils/violation.ts +++ b/typescript/infra/src/utils/violation.ts @@ -98,17 +98,28 @@ function toLowerCaseValues(obj: any): any { }, {}); } -function sortArraysByType(obj: AnyObject): AnyObject { +function sortArraysByType(obj: any): any { if (Array.isArray(obj)) { - return obj - .sort((a, b) => { - if (a.type < b.type) return -1; - if (a.type > b.type) return 1; - return 0; - }) - .map((item) => sortArraysByType(item)); + // Check if array elements are objects with a 'type' property + if ( + obj.length > 0 && + typeof obj[0] === 'object' && + obj[0] !== null && + 'type' in obj[0] + ) { + return obj + .sort((a, b) => { + if (a.type < b.type) return -1; + if (a.type > b.type) return 1; + return 0; + }) + .map((item) => sortArraysByType(item)); + } else { + // For all other arrays, sort normally + return obj.sort().map((item) => sortArraysByType(item)); + } } else if (typeof obj === 'object' && obj !== null) { - const sortedObj: AnyObject = {}; + const sortedObj: any = {}; Object.keys(obj).forEach((key) => { sortedObj[key] = sortArraysByType(obj[key]); }); @@ -259,7 +270,9 @@ export function logViolationDetails(violations: CheckerViolation[]): void { if (violation.type === CoreViolationType.Mailbox) { const mailboxViolation = violation as MailboxViolation; if (mailboxViolation.subType === MailboxViolationType.DefaultIsm) { - console.log(`Mailbox violation ${mailboxViolation.subType} details:`); + console.log( + `${violation.chain} mailbox violation ${mailboxViolation.subType} details:`, + ); logViolationDetail(violation); } } @@ -267,7 +280,9 @@ export function logViolationDetails(violations: CheckerViolation[]): void { if ( violation.type === ConnectionClientViolationType.InterchainSecurityModule ) { - console.log(`Connection client violation ${violation.type} details:`); + console.log( + `${violation.chain} connection client violation ${violation.type} details:`, + ); logViolationDetail(violation); } } diff --git a/typescript/sdk/src/contracts/contracts.ts b/typescript/sdk/src/contracts/contracts.ts index 7a58b3bfd..90b754e8c 100644 --- a/typescript/sdk/src/contracts/contracts.ts +++ b/typescript/sdk/src/contracts/contracts.ts @@ -131,13 +131,43 @@ export function attachContractsMapAndGetForeignDeployments< factories, ); + // TODO: This function shouldn't need to be aware of application types like collateral / synthetic / native etc. Ideally this should work for any app, not just warp routes. is it safe to assume this is always an object containing 1 key/value pair, and that the value will always be an address? const foreignDeployments = objMap( filterChainMapExcludeProtocol( addressesMap, ProtocolType.Ethereum, metadataManager, ), - (_, addresses) => hexOrBase58ToHex(addresses.router), + (chain, addresses) => { + const router = + addresses.router || + addresses.collateral || + addresses.synthetic || + addresses.native; + const protocolType = metadataManager.tryGetChainMetadata(chain)?.protocol; + + if (!router || typeof router !== 'string') { + throw new Error(`Router address not found for ${chain}`); + } + + if (!protocolType) { + throw new Error(`Protocol type not found for ${chain}`); + } + + switch (protocolType) { + case ProtocolType.Ethereum: + throw new Error('Ethereum chain should not have foreign deployments'); + + case ProtocolType.Cosmos: + return router; + + case ProtocolType.Sealevel: + return hexOrBase58ToHex(router); + + default: + throw new Error(`Unsupported protocol type: ${protocolType}`); + } + }, ); return { diff --git a/typescript/sdk/src/router/HyperlaneRouterChecker.ts b/typescript/sdk/src/router/HyperlaneRouterChecker.ts index 519e19c6b..94ec6fdd9 100644 --- a/typescript/sdk/src/router/HyperlaneRouterChecker.ts +++ b/typescript/sdk/src/router/HyperlaneRouterChecker.ts @@ -94,13 +94,22 @@ export class HyperlaneRouterChecker< actualConfig = await ismReader.deriveIsmConfig(actualIsmAddress); } + let expectedConfig = config.interchainSecurityModule; + + if (typeof expectedConfig === 'string') { + expectedConfig = await ismReader.deriveIsmConfig(expectedConfig); + } + + if (expectedConfig === undefined) { + expectedConfig = ethers.constants.AddressZero; + } + const violation: ClientViolation = { chain, type: ClientViolationType.InterchainSecurityModule, contract: router, actual: actualConfig, - expected: - config.interchainSecurityModule ?? ethers.constants.AddressZero, + expected: expectedConfig, description: `ISM config does not match deployed ISM`, }; this.addViolation(violation); diff --git a/typescript/sdk/src/token/app.ts b/typescript/sdk/src/token/app.ts index 0eeb41dbc..3aa061e18 100644 --- a/typescript/sdk/src/token/app.ts +++ b/typescript/sdk/src/token/app.ts @@ -1,5 +1,8 @@ +import { Logger } from 'pino'; + import { TokenRouter } from '@hyperlane-xyz/core'; -import { objKeys } from '@hyperlane-xyz/utils'; +import { ChainMap } from '@hyperlane-xyz/sdk'; +import { Address, objKeys } from '@hyperlane-xyz/utils'; import { appFromAddressesMapHelper } from '../contracts/contracts.js'; import { @@ -16,8 +19,10 @@ export class HypERC20App extends GasRouterApp { constructor( contractsMap: HyperlaneContractsMap, multiProvider: MultiProvider, + logger?: Logger, + foreignDeployments: ChainMap
= {}, ) { - super(contractsMap, multiProvider); + super(contractsMap, multiProvider, logger, foreignDeployments); } router(contracts: HyperlaneContracts): TokenRouter {