diff --git a/multichain-testing/test/send-anywhere.test.ts b/multichain-testing/test/send-anywhere.test.ts index 376853600f0..ba74b23338a 100644 --- a/multichain-testing/test/send-anywhere.test.ts +++ b/multichain-testing/test/send-anywhere.test.ts @@ -10,8 +10,6 @@ import { createWallet } from '../tools/wallet.js'; import { AmountMath } from '@agoric/ertp'; import { makeQueryClient } from '../tools/query.js'; import type { Amount } from '@agoric/ertp/src/types.js'; -import chainInfo from '../starship-chain-info.js'; -import { denomHash, withChainCapabilities } from '@agoric/orchestration'; const test = anyTest as TestFn; @@ -22,39 +20,14 @@ const contractBuilder = '../packages/builders/scripts/testing/init-send-anywhere.js'; test.before(async t => { - const { deleteTestKeys, setupTestKeys, ...rest } = await commonSetup(t); + const { setupTestKeys, ...common } = await commonSetup(t); + const { assetInfo, chainInfo, deleteTestKeys, startContract } = common; deleteTestKeys(accounts).catch(); const wallets = await setupTestKeys(accounts); - t.context = { ...rest, wallets, deleteTestKeys }; - const { startContract } = rest; - - const assetInfo = { - uosmo: { - baseName: 'osmosis', - chainName: 'osmosis', - baseDenom: 'uosmo', - }, - [`ibc/${denomHash({ denom: 'uosmo', channelId: chainInfo.agoric.connections['osmosislocal'].transferChannel.channelId })}`]: - { - baseName: 'osmosis', - chainName: 'agoric', - baseDenom: 'uosmo', - }, - uatom: { - baseName: 'cosmoshub', - chainName: 'cosmoshub', - baseDenom: 'uatom', - }, - [`ibc/${denomHash({ denom: 'uatom', channelId: chainInfo.agoric.connections['gaialocal'].transferChannel.channelId })}`]: - { - baseName: 'cosmoshub', - chainName: 'agoric', - baseDenom: 'uatom', - }, - }; + t.context = { ...common, wallets }; await startContract(contractName, contractBuilder, { - chainInfo: JSON.stringify(withChainCapabilities(chainInfo)), + chainInfo: JSON.stringify(chainInfo), assetInfo: JSON.stringify(assetInfo), }); }); diff --git a/multichain-testing/test/support.ts b/multichain-testing/test/support.ts index f98b5a13f08..aef2bfab173 100644 --- a/multichain-testing/test/support.ts +++ b/multichain-testing/test/support.ts @@ -3,6 +3,7 @@ import { dirname, join } from 'path'; import { execa } from 'execa'; import fse from 'fs-extra'; import childProcess from 'node:child_process'; +import { withChainCapabilities } from '@agoric/orchestration'; import { makeAgdTools } from '../tools/agd-tools.js'; import { type E2ETools } from '../tools/e2e-tools.js'; import { @@ -15,6 +16,8 @@ import { makeRetryUntilCondition } from '../tools/sleep.js'; import { makeDeployBuilder } from '../tools/deploy.js'; import { makeHermes } from '../tools/hermes-tools.js'; import { makeNobleTools } from '../tools/noble-tools.js'; +import { makeAssetInfo } from '../tools/asset-info.js'; +import starshipChainInfo from '../starship-chain-info.js'; export const FAUCET_POUR = 10_000n * 1_000_000n; @@ -78,6 +81,8 @@ export const commonSetup = async (t: ExecutionContext) => { }); const hermes = makeHermes(childProcess); const nobleTools = makeNobleTools(childProcess); + const assetInfo = makeAssetInfo(starshipChainInfo); + const chainInfo = withChainCapabilities(starshipChainInfo); /** * Starts a contract if instance not found. Takes care of installing @@ -116,6 +121,8 @@ export const commonSetup = async (t: ExecutionContext) => { hermes, nobleTools, startContract, + assetInfo, + chainInfo, }; }; diff --git a/multichain-testing/test/tools/asset-info.test.ts b/multichain-testing/test/tools/asset-info.test.ts new file mode 100644 index 00000000000..f12748de7fd --- /dev/null +++ b/multichain-testing/test/tools/asset-info.test.ts @@ -0,0 +1,167 @@ +import test from '@endo/ses-ava/prepare-endo.js'; +import type { Denom, DenomDetail } from '@agoric/orchestration'; +import { makeAssetInfo } from '../../tools/asset-info.js'; + +const minChainInfo = { + agoric: { + chainId: 'agoriclocal', + connections: { + gaialocal: { + transferChannel: { + channelId: 'channel-1', + }, + }, + osmosislocal: { + transferChannel: { + channelId: 'channel-0', + }, + }, + }, + }, + cosmoshub: { + chainId: 'gaialocal', + connections: { + agoriclocal: { + transferChannel: { + channelId: 'channel-1', + }, + }, + osmosislocal: { + transferChannel: { + channelId: 'channel-0', + }, + }, + }, + }, + osmosis: { + chainId: 'osmosislocal', + connections: { + agoriclocal: { + transferChannel: { + channelId: 'channel-1', + }, + }, + gaialocal: { + transferChannel: { + channelId: 'channel-0', + }, + }, + }, + }, +}; + +const minTokenMap = { + agoric: ['ubld', 'uist'], + cosmoshub: ['uatom'], + osmosis: ['uosmo'], +}; + +test('makeAssetInfo', async t => { + const byDenom = (assetInfo: [Denom, DenomDetail][]) => + assetInfo.sort(([a], [b]) => a.localeCompare(b) * -1); + + const assetInfo = makeAssetInfo( + /** @ts-expect-error minified mock */ + minChainInfo, + minTokenMap, + ); + + t.deepEqual(byDenom([...assetInfo]), [ + [ + 'uosmo', + { + baseDenom: 'uosmo', + baseName: 'osmosis', + chainName: 'osmosis', + }, + ], + [ + 'uist', + { + baseDenom: 'uist', + baseName: 'agoric', + chainName: 'agoric', + }, + ], + [ + 'ubld', + { + baseDenom: 'ubld', + baseName: 'agoric', + chainName: 'agoric', + }, + ], + [ + 'uatom', + { + baseDenom: 'uatom', + baseName: 'cosmoshub', + chainName: 'cosmoshub', + }, + ], + [ + 'ibc/ED07A3391A112B175915CD8FAF43A2DA8E4790EDE12566649D0C2F97716B8518', + { + baseDenom: 'uosmo', + baseName: 'osmosis', + chainName: 'agoric', + }, + ], + [ + 'ibc/ED07A3391A112B175915CD8FAF43A2DA8E4790EDE12566649D0C2F97716B8518', + { + baseDenom: 'uosmo', + baseName: 'osmosis', + chainName: 'cosmoshub', + }, + ], + [ + 'ibc/E7827844CB818EE9C4DB2C159F1543FF62B26213B44CE8029D5CEFE52F0EE596', + { + baseDenom: 'ubld', + baseName: 'agoric', + chainName: 'cosmoshub', + }, + ], + [ + 'ibc/E7827844CB818EE9C4DB2C159F1543FF62B26213B44CE8029D5CEFE52F0EE596', + { + baseDenom: 'ubld', + baseName: 'agoric', + chainName: 'osmosis', + }, + ], + [ + 'ibc/C4CFF46FD6DE35CA4CF4CE031E643C8FDC9BA4B99AE598E9B0ED98FE3A2319F9', + { + baseDenom: 'uatom', + baseName: 'cosmoshub', + chainName: 'agoric', + }, + ], + [ + 'ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2', + { + baseDenom: 'uatom', + baseName: 'cosmoshub', + chainName: 'osmosis', + }, + ], + [ + 'ibc/16CD81E12F05F5397CA2D580B4BA786A12A8F48B6FB3823D82EBE95D80B5287B', + { + baseDenom: 'uist', + baseName: 'agoric', + chainName: 'cosmoshub', + }, + ], + [ + 'ibc/16CD81E12F05F5397CA2D580B4BA786A12A8F48B6FB3823D82EBE95D80B5287B', + { + baseDenom: 'uist', + baseName: 'agoric', + chainName: 'osmosis', + }, + ], + ]); +}); diff --git a/multichain-testing/tools/asset-info.ts b/multichain-testing/tools/asset-info.ts new file mode 100644 index 00000000000..34c7d536b8c --- /dev/null +++ b/multichain-testing/tools/asset-info.ts @@ -0,0 +1,76 @@ +import { + denomHash, + type CosmosChainInfo, + type Denom, + type DenomDetail, +} from '@agoric/orchestration'; +import type { IBCChannelID } from '@agoric/vats'; + +/** make asset info for current env */ +export const makeAssetInfo = ( + chainInfo: Record, + tokenMap: Record = { + agoric: ['ubld', 'uist'], + cosmoshub: ['uatom'], + noble: ['uusdc'], + osmosis: ['uosmo', 'uion'], + }, +): [Denom, DenomDetail][] => { + const getChannelId = ( + issuingChainId: string, + holdingChainName: string, + ): IBCChannelID | undefined => + chainInfo[holdingChainName]?.connections?.[issuingChainId]?.transferChannel + .channelId; + + const toDenomHash = ( + denom: Denom, + issuingChainId: string, + holdingChainName: string, + ): Denom => { + const channelId = getChannelId(issuingChainId, holdingChainName); + if (!channelId) { + throw new Error( + `No channel found for ${issuingChainId} -> ${holdingChainName}`, + ); + } + return `ibc/${denomHash({ denom, channelId })}`; + }; + + // only include chains present in `chainInfo` + const tokens = Object.entries(tokenMap) + .filter(([chain]) => chain in chainInfo) + .flatMap(([chain, denoms]) => denoms.map(denom => ({ denom, chain }))); + + const assetInfo: [Denom, DenomDetail][] = []; + for (const { denom, chain } of tokens) { + const baseDetails = { + baseName: chain, + baseDenom: denom, + }; + + // Add native token entry + assetInfo.push([ + denom, + { + ...baseDetails, + chainName: chain, + }, + ]); + + // Add IBC entries for non-issuing chains + const issuingChainId = chainInfo[chain].chainId; + for (const holdingChain of Object.keys(chainInfo)) { + if (holdingChain === chain) continue; + assetInfo.push([ + toDenomHash(denom, issuingChainId, holdingChain), + { + ...baseDetails, + chainName: holdingChain, + }, + ]); + } + } + + return harden(assetInfo); +};