diff --git a/a3p-integration/proposals/f:fast-usdc/package.json b/a3p-integration/proposals/f:fast-usdc/package.json index 6d5c1a0fc64..f8ce07cad71 100644 --- a/a3p-integration/proposals/f:fast-usdc/package.json +++ b/a3p-integration/proposals/f:fast-usdc/package.json @@ -1,9 +1,7 @@ { "agoricProposal": { "source": "subdir", - "$UNTIL": "write-chain-info to agoricNames until #10445 and chainHub setup", "sdk-generate": [ - "orchestration/write-chain-info.js", "fast-usdc/init-fast-usdc.js submission --net A3P_INTEGRATION" ], "type": "/agoric.swingset.CoreEvalProposal" diff --git a/multichain-testing/test/auto-stake-it.test.ts b/multichain-testing/test/auto-stake-it.test.ts index f7b3ff9ba7c..29b984ceec0 100644 --- a/multichain-testing/test/auto-stake-it.test.ts +++ b/multichain-testing/test/auto-stake-it.test.ts @@ -1,7 +1,6 @@ -import type { CosmosChainInfo } from '@agoric/orchestration'; import anyTest from '@endo/ses-ava/prepare-endo.js'; import type { ExecutionContext, TestFn } from 'ava'; -import chainInfo from '../starship-chain-info.js'; +import starshipChainInfo from '../starship-chain-info.js'; import { makeDoOffer } from '../tools/e2e-tools.js'; import { createFundedWalletAndClient, @@ -18,15 +17,19 @@ const accounts = ['agoricAdmin', 'cosmoshub', 'osmosis']; const contractName = 'autoAutoStakeIt'; const contractBuilder = - '../packages/builders/scripts/testing/start-auto-stake-it.js'; + '../packages/builders/scripts/testing/init-auto-stake-it.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; - await startContract(contractName, contractBuilder); + t.context = { ...common, wallets }; + + await startContract(contractName, contractBuilder, { + chainInfo, + assetInfo, + }); }); test.after(async t => { @@ -96,9 +99,7 @@ const autoStakeItScenario = test.macro({ const fundAndTransfer = makeFundAndTransfer(t); // 2. Find 'stakingDenom' denom on agoric - const remoteChainInfo = (chainInfo as Record)[ - chainName - ]; + const remoteChainInfo = starshipChainInfo[chainName]; const stakingDenom = remoteChainInfo?.stakingTokens?.[0].denom; if (!stakingDenom) throw Error(`staking denom found for ${chainName}`); diff --git a/multichain-testing/test/basic-flows.test.ts b/multichain-testing/test/basic-flows.test.ts index 8db6ef7c84e..689db323524 100644 --- a/multichain-testing/test/basic-flows.test.ts +++ b/multichain-testing/test/basic-flows.test.ts @@ -16,12 +16,15 @@ const contractBuilder = '../packages/builders/scripts/orchestration/init-basic-flows.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; - await startContract(contractName, contractBuilder); + t.context = { ...common, wallets }; + await startContract(contractName, contractBuilder, { + chainInfo, + assetInfo, + }); }); test.after(async t => { diff --git a/multichain-testing/test/deposit-withdraw-lca.test.ts b/multichain-testing/test/deposit-withdraw-lca.test.ts index 9ba574dff56..ad7fd4824d9 100644 --- a/multichain-testing/test/deposit-withdraw-lca.test.ts +++ b/multichain-testing/test/deposit-withdraw-lca.test.ts @@ -14,12 +14,15 @@ const contractBuilder = '../packages/builders/scripts/orchestration/init-basic-flows.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; - await startContract(contractName, contractBuilder); + t.context = { ...common, wallets }; + await startContract(contractName, contractBuilder, { + chainInfo, + assetInfo, + }); }); test.after(async t => { diff --git a/multichain-testing/test/deposit-withdraw-portfolio.test.ts b/multichain-testing/test/deposit-withdraw-portfolio.test.ts index 24bb5277a80..ea97f1e7f17 100644 --- a/multichain-testing/test/deposit-withdraw-portfolio.test.ts +++ b/multichain-testing/test/deposit-withdraw-portfolio.test.ts @@ -14,12 +14,15 @@ const contractBuilder = '../packages/builders/scripts/orchestration/init-basic-flows.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; - await startContract(contractName, contractBuilder); + t.context = { ...common, wallets }; + await startContract(contractName, contractBuilder, { + chainInfo, + assetInfo, + }); }); test.after(async t => { diff --git a/multichain-testing/test/ica-channel-close.test.ts b/multichain-testing/test/ica-channel-close.test.ts index 9e844fa903b..03b0ef9ac95 100644 --- a/multichain-testing/test/ica-channel-close.test.ts +++ b/multichain-testing/test/ica-channel-close.test.ts @@ -22,12 +22,15 @@ const contractBuilder = '../packages/builders/scripts/orchestration/init-basic-flows.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; - await startContract(contractName, contractBuilder); + t.context = { ...common, wallets }; + await startContract(contractName, contractBuilder, { + chainInfo, + assetInfo, + }); }); test.after(async t => { diff --git a/multichain-testing/test/send-anywhere.test.ts b/multichain-testing/test/send-anywhere.test.ts index ba74b23338a..f1a8a720e5f 100644 --- a/multichain-testing/test/send-anywhere.test.ts +++ b/multichain-testing/test/send-anywhere.test.ts @@ -27,8 +27,8 @@ test.before(async t => { t.context = { ...common, wallets }; await startContract(contractName, contractBuilder, { - chainInfo: JSON.stringify(chainInfo), - assetInfo: JSON.stringify(assetInfo), + chainInfo, + assetInfo, }); }); diff --git a/multichain-testing/test/support.ts b/multichain-testing/test/support.ts index aef2bfab173..f3f8e752d96 100644 --- a/multichain-testing/test/support.ts +++ b/multichain-testing/test/support.ts @@ -4,6 +4,7 @@ import { execa } from 'execa'; import fse from 'fs-extra'; import childProcess from 'node:child_process'; import { withChainCapabilities } from '@agoric/orchestration'; +import { objectMap } from '@endo/patterns'; import { makeAgdTools } from '../tools/agd-tools.js'; import { type E2ETools } from '../tools/e2e-tools.js'; import { @@ -94,7 +95,7 @@ export const commonSetup = async (t: ExecutionContext) => { const startContract = async ( contractName: string, contractBuilder: string, - builderOpts?: Record, + builderOpts?: Record, ) => { const { vstorageClient } = tools; const instances = Object.fromEntries( @@ -104,7 +105,18 @@ export const commonSetup = async (t: ExecutionContext) => { return t.log('Contract found. Skipping installation...'); } t.log('bundle and install contract', contractName); - await deployBuilder(contractBuilder, builderOpts); + + const formattedOpts = builderOpts + ? objectMap( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + builderOpts as Record, + value => { + if (typeof value === 'string') return value; + return JSON.stringify(value); + }, + ) + : undefined; + await deployBuilder(contractBuilder, formattedOpts); await retryUntilCondition( () => vstorageClient.queryData(`published.agoricNames.instance`), res => contractName in Object.fromEntries(res), diff --git a/multichain-testing/test/tools/asset-info.test.ts b/multichain-testing/test/tools/asset-info.test.ts index f12748de7fd..be054b65180 100644 --- a/multichain-testing/test/tools/asset-info.test.ts +++ b/multichain-testing/test/tools/asset-info.test.ts @@ -80,6 +80,7 @@ test('makeAssetInfo', async t => { { baseDenom: 'uist', baseName: 'agoric', + brandKey: 'IST', chainName: 'agoric', }, ], @@ -88,6 +89,7 @@ test('makeAssetInfo', async t => { { baseDenom: 'ubld', baseName: 'agoric', + brandKey: 'BLD', chainName: 'agoric', }, ], diff --git a/multichain-testing/tools/asset-info.ts b/multichain-testing/tools/asset-info.ts index 34c7d536b8c..f3e548d1968 100644 --- a/multichain-testing/tools/asset-info.ts +++ b/multichain-testing/tools/asset-info.ts @@ -37,6 +37,13 @@ export const makeAssetInfo = ( return `ibc/${denomHash({ denom, channelId })}`; }; + // `brandKey` instead of `brand` until #10580 + // only BLD, IST until #9966 + const BRAND_KEY_MAP: Record = { + ubld: 'BLD', + uist: 'IST', + }; + // only include chains present in `chainInfo` const tokens = Object.entries(tokenMap) .filter(([chain]) => chain in chainInfo) @@ -55,6 +62,7 @@ export const makeAssetInfo = ( { ...baseDetails, chainName: chain, + ...(BRAND_KEY_MAP[denom] && { brandKey: BRAND_KEY_MAP[denom] }), }, ]); @@ -62,11 +70,15 @@ export const makeAssetInfo = ( const issuingChainId = chainInfo[chain].chainId; for (const holdingChain of Object.keys(chainInfo)) { if (holdingChain === chain) continue; + const denomHash = toDenomHash(denom, issuingChainId, holdingChain); assetInfo.push([ - toDenomHash(denom, issuingChainId, holdingChain), + denomHash, { ...baseDetails, chainName: holdingChain, + ...(BRAND_KEY_MAP[denomHash] && { + brandKey: BRAND_KEY_MAP[denomHash], + }), }, ]); } diff --git a/packages/boot/test/bootstrapTests/orchestration.test.ts b/packages/boot/test/bootstrapTests/orchestration.test.ts index b51f13e6568..76c5d152c59 100644 --- a/packages/boot/test/bootstrapTests/orchestration.test.ts +++ b/packages/boot/test/bootstrapTests/orchestration.test.ts @@ -5,11 +5,17 @@ import { defaultMarshaller, documentStorageSchema, } from '@agoric/internal/src/storage-test-utils.js'; -import type { CosmosValidatorAddress } from '@agoric/orchestration'; +import { + withChainCapabilities, + type CosmosValidatorAddress, +} from '@agoric/orchestration'; import type { start as startStakeIca } from '@agoric/orchestration/src/examples/stake-ica.contract.js'; import type { Instance } from '@agoric/zoe/src/zoeService/utils.js'; import type { TestFn } from 'ava'; import { SIMULATED_ERRORS } from '@agoric/vats/tools/fake-bridge.js'; +import fetchedChainInfo from '@agoric/orchestration/src/fetched-chain-info.js'; +import { buildVTransferEvent } from '@agoric/orchestration/tools/ibc-mocks.js'; +import { BridgeId } from '@agoric/internal'; import { makeWalletFactoryContext, type WalletFactoryTestContext, @@ -266,7 +272,9 @@ test.serial('stakeAtom - smart wallet', async t => { proposal: {}, }), { - message: 'No denom for brand [object Alleged: ATOM brand]', + // TODO #10449 + message: + "'amountToCoin' not working for \"[Alleged: ATOM brand]\" until #10449; use 'DenomAmount' for now", }, ); }); @@ -308,17 +316,44 @@ test.serial('revise chain info', async t => { }); }); -test('basic-flows', async t => { +test.serial('basic-flows', async t => { const { buildProposal, evalProposal, agoricNamesRemotes, readPublished, - bridgeUtils: { flushInboundQueue }, + bridgeUtils: { flushInboundQueue, runInbound }, } = t.context; await evalProposal( - buildProposal('@agoric/builders/scripts/orchestration/init-basic-flows.js'), + buildProposal( + '@agoric/builders/scripts/orchestration/init-basic-flows.js', + [ + '--chainInfo', + JSON.stringify(withChainCapabilities(fetchedChainInfo)), + '--assetInfo', + JSON.stringify([ + [ + 'ibc/uusdconagoric', + { + chainName: 'agoric', + baseName: 'noble', + baseDenom: 'uusdc', + }, + ], + // not tested until #10006. consider renaming to ibc/uusdconcosmos + // and updating boot/tools/ibc/mocks.ts + [ + 'ibc/uusdchash', + { + chainName: 'cosmoshub', + baseName: 'noble', + baseDenom: 'uusdc', + }, + ], + ]), + ], + ), ); const wd = @@ -353,9 +388,9 @@ test('basic-flows', async t => { }); t.deepEqual(readPublished('basicFlows.cosmos1test'), { localAddress: - '/ibc-port/icacontroller-4/ordered/{"version":"ics27-1","controllerConnectionId":"connection-8","hostConnectionId":"connection-649","address":"cosmos1test","encoding":"proto3","txType":"sdk_multi_msg"}/ibc-channel/channel-4', + '/ibc-port/icacontroller-2/ordered/{"version":"ics27-1","controllerConnectionId":"connection-8","hostConnectionId":"connection-649","address":"cosmos1test","encoding":"proto3","txType":"sdk_multi_msg"}/ibc-channel/channel-2', remoteAddress: - '/ibc-hop/connection-8/ibc-port/icahost/ordered/{"version":"ics27-1","controllerConnectionId":"connection-8","hostConnectionId":"connection-649","address":"cosmos1test","encoding":"proto3","txType":"sdk_multi_msg"}/ibc-channel/channel-4', + '/ibc-hop/connection-8/ibc-port/icahost/ordered/{"version":"ics27-1","controllerConnectionId":"connection-8","hostConnectionId":"connection-649","address":"cosmos1test","encoding":"proto3","txType":"sdk_multi_msg"}/ibc-channel/channel-2', }); // create a local orchestration account @@ -376,12 +411,11 @@ test('basic-flows', async t => { wd.getCurrentWalletRecord().offerToPublicSubscriberPaths, ); t.deepEqual(publicSubscriberPaths['request-loa'], { - account: 'published.basicFlows.agoric1fakeLCAAddress1', + account: 'published.basicFlows.agoric1fakeLCAAddress', }); t.like(wd.getLatestUpdateRecord(), { status: { id: 'request-loa', numWantsSatisfied: 1 }, }); - t.is(readPublished('basicFlows.agoric1fakeLCAAddress'), ''); await wd.sendOffer({ id: 'transfer-to-noble-from-cosmos', @@ -441,7 +475,7 @@ test('basic-flows', async t => { }, proposal: {}, offerArgs: { - amount: { denom: 'ibc/uusdchash', value: 10n }, + amount: { denom: 'ibc/uusdconagoric', value: 10n }, destination: { chainId: 'noble-1', value: 'noble1test', @@ -449,12 +483,29 @@ test('basic-flows', async t => { }, }, }); - t.like(wd.getLatestUpdateRecord(), { - status: { - id: 'transfer-to-noble-from-agoric', - error: undefined, - }, + + await runInbound( + BridgeId.VTRANSFER, + buildVTransferEvent({ + sourceChannel: 'channel-62', + sequence: '1', + }), + ); + + const latestOfferStatus = () => { + const curr = wd.getLatestUpdateRecord(); + if (curr.updated === 'offerStatus') { + return curr.status; + } + throw new Error('expected updated to be "offerStatus"'); + }; + + const offerResult = latestOfferStatus(); + t.like(offerResult, { + id: 'transfer-to-noble-from-agoric', + error: undefined, }); + t.true('result' in offerResult, 'transfer vow settled'); await t.throwsAsync( wd.executeOffer({ @@ -466,7 +517,7 @@ test('basic-flows', async t => { }, proposal: {}, offerArgs: { - amount: { denom: 'ibc/uusdchash', value: SIMULATED_ERRORS.TIMEOUT }, + amount: { denom: 'ibc/uusdconagoric', value: SIMULATED_ERRORS.TIMEOUT }, destination: { chainId: 'noble-1', value: 'noble1test', @@ -482,7 +533,7 @@ test.serial('auto-stake-it - proposal', async t => { await t.notThrowsAsync( evalProposal( - buildProposal('@agoric/builders/scripts/testing/start-auto-stake-it.js'), + buildProposal('@agoric/builders/scripts/testing/init-auto-stake-it.js'), ), ); }); @@ -497,7 +548,25 @@ test.serial('basic-flows - portfolio holder', async t => { } = t.context; await evalProposal( - buildProposal('@agoric/builders/scripts/orchestration/init-basic-flows.js'), + buildProposal( + '@agoric/builders/scripts/orchestration/init-basic-flows.js', + [ + '--chainInfo', + JSON.stringify(withChainCapabilities(fetchedChainInfo)), + '--assetInfo', + JSON.stringify([ + [ + 'ubld', + { + baseDenom: 'ubld', + baseName: 'agoric', + chainName: 'agoric', + brandKey: 'BLD', + }, + ], + ]), + ], + ), ); const wd = @@ -529,7 +598,7 @@ test.serial('basic-flows - portfolio holder', async t => { [ 'request-portfolio-acct', { - agoric: 'published.basicFlows.agoric1fakeLCAAddress', + agoric: 'published.basicFlows.agoric1fakeLCAAddress1', cosmoshub: 'published.basicFlows.cosmos1test', // XXX support multiple chain addresses in ibc mocks osmosis: 'published.basicFlows.cosmos1test', @@ -543,9 +612,9 @@ test.serial('basic-flows - portfolio holder', async t => { // XXX this overrides a previous account, since mocks only provide one address t.deepEqual(readPublished('basicFlows.cosmos1test'), { localAddress: - '/ibc-port/icacontroller-3/ordered/{"version":"ics27-1","controllerConnectionId":"connection-1","hostConnectionId":"connection-1649","address":"cosmos1test","encoding":"proto3","txType":"sdk_multi_msg"}/ibc-channel/channel-3', + '/ibc-port/icacontroller-4/ordered/{"version":"ics27-1","controllerConnectionId":"connection-1","hostConnectionId":"connection-1649","address":"cosmos1test","encoding":"proto3","txType":"sdk_multi_msg"}/ibc-channel/channel-4', remoteAddress: - '/ibc-hop/connection-1/ibc-port/icahost/ordered/{"version":"ics27-1","controllerConnectionId":"connection-1","hostConnectionId":"connection-1649","address":"cosmos1test","encoding":"proto3","txType":"sdk_multi_msg"}/ibc-channel/channel-3', + '/ibc-hop/connection-1/ibc-port/icahost/ordered/{"version":"ics27-1","controllerConnectionId":"connection-1","hostConnectionId":"connection-1649","address":"cosmos1test","encoding":"proto3","txType":"sdk_multi_msg"}/ibc-channel/channel-4', }); // XXX this overrides a previous account, since mocks only provide one address t.is(readPublished('basicFlows.agoric1fakeLCAAddress'), ''); diff --git a/packages/boot/test/orchestration/contract-upgrade.test.ts b/packages/boot/test/orchestration/contract-upgrade.test.ts index ada1a4a4144..65293a800b7 100644 --- a/packages/boot/test/orchestration/contract-upgrade.test.ts +++ b/packages/boot/test/orchestration/contract-upgrade.test.ts @@ -4,6 +4,8 @@ import type { TestFn } from 'ava'; import { BridgeId } from '@agoric/internal'; import { buildVTransferEvent } from '@agoric/orchestration/tools/ibc-mocks.js'; +import fetchedChainInfo from '@agoric/orchestration/src/fetched-chain-info.js'; +import { withChainCapabilities } from '@agoric/orchestration'; import { makeWalletFactoryContext, type WalletFactoryTestContext, @@ -19,17 +21,13 @@ test.before(async t => { test.after.always(t => t.context.shutdown?.()); /** - * This test core-evals a buggy installation of the sendAnywhere contract by - * giving it a faulty `agoricNames` service with a lookup() function which - * returns a promise that never resolves. + * This test core-evals an installation of the sendAnywhere contract that + * initiates an IBC Transfer. Since that goes over a bridge and is tracked + * by a vow, we can restart the contract and see that the vow settles. We + * can manually trigger a bridge event in the testing context. * - * Because the send-anywhere flow requires a lookup(), it waits forever. This - * gives us a point at which we can upgrade the vat with a working agoricNames - * and see that the flow continues from that point. (The lookup call is not made - * directly in a flow, but instead from a host API which uses the retryable - * helper. As such it tests both the idempotent retry mechanism of retryable on - * upgrades, and the ability to resume an async-flow for which a host vow - * settles after an upgrade.) + * As such, this demonstrates the ability to resume an async-flow for for which + * a host vow settles after an upgrade. */ test('resume', async t => { const { @@ -44,9 +42,21 @@ test('resume', async t => { t.log('start sendAnywhere'); await evalProposal( - buildProposal( - '@agoric/builders/scripts/testing/start-buggy-sendAnywhere.js', - ), + buildProposal('@agoric/builders/scripts/testing/init-send-anywhere.js', [ + '--chainInfo', + JSON.stringify(withChainCapabilities(fetchedChainInfo)), + '--assetInfo', + JSON.stringify([ + [ + 'uist', + { + baseDenom: 'uist', + baseName: 'agoric', + chainName: 'agoric', + }, + ], + ]), + ]), ); t.log('making offer'); @@ -70,16 +80,9 @@ test('resume', async t => { // XXX golden test const getLogged = () => - JSON.parse(storage.data.get('published.sendAnywhere.log')!).values; - - // This log shows the flow started, but didn't get past the name lookup - t.deepEqual(getLogged(), ['sending {0} from cosmoshub to cosmos1whatever']); - - t.log('upgrade sendAnywhere with fix'); - await evalProposal( - buildProposal('@agoric/builders/scripts/testing/fix-buggy-sendAnywhere.js'), - ); + JSON.parse(storage.data.get('published.send-anywhere.log')!).values; + // This log shows the flow started, but didn't get past the IBC Transfer settlement t.deepEqual(getLogged(), [ 'sending {0} from cosmoshub to cosmos1whatever', 'got info for denoms: ibc/FE98AAD68F02F03565E9FA39A5E627946699B2B07115889ED812D8BA639576A9, ibc/toyatom, ibc/toyusdc, ubld, uist', @@ -87,6 +90,11 @@ test('resume', async t => { 'completed transfer to localAccount', ]); + t.log('null upgrading sendAnywhere'); + await evalProposal( + buildProposal('@agoric/builders/scripts/testing/upgrade-send-anywhere.js'), + ); + // simulate ibc/MsgTransfer ack from remote chain, enabling `.transfer()` promise // to resolve await runInbound( diff --git a/packages/boot/test/orchestration/restart-contracts.test.ts b/packages/boot/test/orchestration/restart-contracts.test.ts index 790f58e5d44..b727b8a74be 100644 --- a/packages/boot/test/orchestration/restart-contracts.test.ts +++ b/packages/boot/test/orchestration/restart-contracts.test.ts @@ -3,9 +3,13 @@ import { test as anyTest } from '@agoric/zoe/tools/prepare-test-env-ava.js'; import type { TestFn } from 'ava'; import { BridgeId } from '@agoric/internal'; -import type { CosmosValidatorAddress } from '@agoric/orchestration'; +import { + withChainCapabilities, + type CosmosValidatorAddress, +} from '@agoric/orchestration'; import { buildVTransferEvent } from '@agoric/orchestration/tools/ibc-mocks.js'; import type { UpdateRecord } from '@agoric/smart-wallet/src/smartWallet.js'; +import fetchedChainInfo from '@agoric/orchestration/src/fetched-chain-info.js'; import { makeWalletFactoryContext, type WalletFactoryTestContext, @@ -32,7 +36,21 @@ test.serial('send-anywhere', async t => { t.log('start send-anywhere'); await evalProposal( - buildProposal('@agoric/builders/scripts/testing/init-send-anywhere.js'), + buildProposal('@agoric/builders/scripts/testing/init-send-anywhere.js', [ + '--chainInfo', + JSON.stringify(withChainCapabilities(fetchedChainInfo)), + '--assetInfo', + JSON.stringify([ + [ + 'uist', + { + baseDenom: 'uist', + baseName: 'agoric', + chainName: 'agoric', + }, + ], + ]), + ]), ); t.log('making offer'); @@ -95,9 +113,12 @@ test.serial('send-anywhere', async t => { id: 'send-somewhere', numWantsSatisfied: 1, error: undefined, - result: undefined, }, }); + if (conclusion.updated !== 'offerStatus') { + throw new Error('expected offerStatus'); + } + t.true('result' in conclusion.status, 'transfer vow settled'); }); const validatorAddress: CosmosValidatorAddress = { diff --git a/packages/builders/scripts/orchestration/init-basic-flows.js b/packages/builders/scripts/orchestration/init-basic-flows.js index ab2de229cf3..430df76140a 100644 --- a/packages/builders/scripts/orchestration/init-basic-flows.js +++ b/packages/builders/scripts/orchestration/init-basic-flows.js @@ -1,8 +1,22 @@ import { makeHelpers } from '@agoric/deploy-script-support'; import { startBasicFlows } from '@agoric/orchestration/src/proposals/start-basic-flows.js'; +import { parseArgs } from 'node:util'; + +/** + * @import {ParseArgsConfig} from 'node:util' + */ + +/** @type {ParseArgsConfig['options']} */ +const parserOpts = { + chainInfo: { type: 'string' }, + assetInfo: { type: 'string' }, +}; /** @type {import('@agoric/deploy-script-support/src/externalTypes.js').CoreEvalBuilder} */ -export const defaultProposalBuilder = async ({ publishRef, install }) => { +export const defaultProposalBuilder = async ( + { publishRef, install }, + options, +) => { return harden({ sourceSpec: '@agoric/orchestration/src/proposals/start-basic-flows.js', getManifestCall: [ @@ -15,6 +29,7 @@ export const defaultProposalBuilder = async ({ publishRef, install }) => { ), ), }, + options, }, ], }); @@ -22,6 +37,31 @@ export const defaultProposalBuilder = async ({ publishRef, install }) => { /** @type {import('@agoric/deploy-script-support/src/externalTypes.js').DeployScriptFunction} */ export default async (homeP, endowments) => { + const { scriptArgs } = endowments; + + const { + values: { chainInfo, assetInfo }, + } = parseArgs({ + args: scriptArgs, + options: parserOpts, + }); + + const parseChainInfo = () => { + if (typeof chainInfo !== 'string') return undefined; + return JSON.parse(chainInfo); + }; + const parseAssetInfo = () => { + if (typeof assetInfo !== 'string') return undefined; + return JSON.parse(assetInfo); + }; + const opts = harden({ + chainInfo: parseChainInfo(), + assetInfo: parseAssetInfo(), + }); + const { writeCoreEval } = await makeHelpers(homeP, endowments); - await writeCoreEval(startBasicFlows.name, defaultProposalBuilder); + + await writeCoreEval(startBasicFlows.name, utils => + defaultProposalBuilder(utils, opts), + ); }; diff --git a/packages/builders/scripts/testing/init-auto-stake-it.js b/packages/builders/scripts/testing/init-auto-stake-it.js new file mode 100644 index 00000000000..8c5a515b8ee --- /dev/null +++ b/packages/builders/scripts/testing/init-auto-stake-it.js @@ -0,0 +1,73 @@ +/** + * @file A proposal to start the auto-stake-it contract. + * + * AutoStakeIt allows users to to create an auto-forwarding address that + * transfers and stakes tokens on a remote chain when received. + */ +import { makeHelpers } from '@agoric/deploy-script-support'; +import { startAutoStakeIt } from '@agoric/orchestration/src/proposals/start-auto-stake-it.js'; +import { parseArgs } from 'node:util'; + +/** + * @import {ParseArgsConfig} from 'node:util' + */ + +/** @type {ParseArgsConfig['options']} */ +const parserOpts = { + chainInfo: { type: 'string' }, + assetInfo: { type: 'string' }, +}; + +/** @type {import('@agoric/deploy-script-support/src/externalTypes.js').CoreEvalBuilder} */ +export const defaultProposalBuilder = async ( + { publishRef, install }, + options, +) => { + return harden({ + sourceSpec: '@agoric/orchestration/src/proposals/start-auto-stake-it.js', + getManifestCall: [ + 'getManifest', + { + installKeys: { + autoAutoStakeIt: publishRef( + install( + '@agoric/orchestration/src/examples/auto-stake-it.contract.js', + ), + ), + }, + options, + }, + ], + }); +}; + +/** @type {import('@agoric/deploy-script-support/src/externalTypes.js').DeployScriptFunction} */ +export default async (homeP, endowments) => { + const { scriptArgs } = endowments; + + const { + values: { chainInfo, assetInfo }, + } = parseArgs({ + args: scriptArgs, + options: parserOpts, + }); + + const parseChainInfo = () => { + if (typeof chainInfo !== 'string') return undefined; + return JSON.parse(chainInfo); + }; + const parseAssetInfo = () => { + if (typeof assetInfo !== 'string') return undefined; + return JSON.parse(assetInfo); + }; + const opts = harden({ + chainInfo: parseChainInfo(), + assetInfo: parseAssetInfo(), + }); + + const { writeCoreEval } = await makeHelpers(homeP, endowments); + + await writeCoreEval(startAutoStakeIt.name, utils => + defaultProposalBuilder(utils, opts), + ); +}; diff --git a/packages/builders/scripts/testing/start-buggy-sendAnywhere.js b/packages/builders/scripts/testing/start-buggy-sendAnywhere.js deleted file mode 100644 index 6f5ac66ee1d..00000000000 --- a/packages/builders/scripts/testing/start-buggy-sendAnywhere.js +++ /dev/null @@ -1,143 +0,0 @@ -/** - * @file This is for use in tests in a3p-integration - * Unlike most builder scripts, this one includes the proposal exports as well. - */ -import { - deeplyFulfilledObject, - makeTracer, - NonNullish, -} from '@agoric/internal'; -import { E, Far } from '@endo/far'; - -/// -/** - * @import {Installation} from '@agoric/zoe/src/zoeService/utils.js'; - */ - -const trace = makeTracer('StartBuggySA', true); - -/** - * @import {start as StartFn} from '@agoric/orchestration/src/examples/send-anywhere.contract.js'; - */ - -/** - * @param {BootstrapPowers & { - * installation: { - * consume: { - * sendAnywhere: Installation; - * }; - * }; - * }} powers - */ -export const startSendAnywhere = async ({ - consume: { - agoricNames, - board, - chainStorage, - chainTimerService, - cosmosInterchainService, - localchain, - startUpgradable, - }, - installation: { - consume: { sendAnywhere }, - }, - instance: { - // @ts-expect-error unknown instance - produce: { sendAnywhere: produceInstance }, - }, -}) => { - trace(startSendAnywhere.name); - - const marshaller = await E(board).getReadonlyMarshaller(); - - const privateArgs = await deeplyFulfilledObject( - harden({ - agoricNames, - localchain, - marshaller, - orchestrationService: cosmosInterchainService, - storageNode: E(NonNullish(await chainStorage)).makeChildNode( - 'sendAnywhere', - ), - timerService: chainTimerService, - }), - ); - - /** @type {import('@agoric/vats').NameHub} */ - // @ts-expect-error intentional fake - const agoricNamesHangs = Far('agoricNames that hangs', { - lookup: async () => { - trace('agoricNames.lookup being called that will never resolve'); - // BUG: this never resolves - return new Promise(() => {}); - }, - }); - - const { instance } = await E(startUpgradable)({ - label: 'sendAnywhere', - installation: sendAnywhere, - privateArgs: { - ...privateArgs, - agoricNames: agoricNamesHangs, - }, - }); - produceInstance.resolve(instance); - trace('done'); -}; -harden(startSendAnywhere); - -export const getManifestForValueVow = ({ restoreRef }, { sendAnywhereRef }) => { - trace('sendAnywhereRef', sendAnywhereRef); - return { - manifest: { - [startSendAnywhere.name]: { - consume: { - agoricNames: true, - board: true, - chainStorage: true, - chainTimerService: true, - cosmosInterchainService: true, - localchain: true, - - startUpgradable: true, - }, - installation: { - consume: { sendAnywhere: true }, - }, - instance: { - produce: { sendAnywhere: true }, - }, - }, - }, - installations: { - sendAnywhere: restoreRef(sendAnywhereRef), - }, - }; -}; - -/** @type {import('@agoric/deploy-script-support/src/externalTypes.js').CoreEvalBuilder} */ -export const defaultProposalBuilder = async ({ publishRef, install }) => - harden({ - // Somewhat unorthodox, source the exports from this builder module - sourceSpec: '@agoric/builders/scripts/testing/start-buggy-sendAnywhere.js', - getManifestCall: [ - 'getManifestForValueVow', - { - sendAnywhereRef: publishRef( - install( - '@agoric/orchestration/src/examples/send-anywhere.contract.js', - ), - ), - }, - ], - }); - -/** @type {import('@agoric/deploy-script-support/src/externalTypes.js').DeployScriptFunction} */ -export default async (homeP, endowments) => { - // import dynamically so the module can work in CoreEval environment - const dspModule = await import('@agoric/deploy-script-support'); - const { makeHelpers } = dspModule; - const { writeCoreEval } = await makeHelpers(homeP, endowments); - await writeCoreEval(startSendAnywhere.name, defaultProposalBuilder); -}; diff --git a/packages/builders/scripts/testing/fix-buggy-sendAnywhere.js b/packages/builders/scripts/testing/upgrade-send-anywhere.js similarity index 81% rename from packages/builders/scripts/testing/fix-buggy-sendAnywhere.js rename to packages/builders/scripts/testing/upgrade-send-anywhere.js index 8daa2dbf516..4acfaf5f8ad 100644 --- a/packages/builders/scripts/testing/fix-buggy-sendAnywhere.js +++ b/packages/builders/scripts/testing/upgrade-send-anywhere.js @@ -7,14 +7,14 @@ import { makeTracer, NonNullish, } from '@agoric/internal'; -import { E, Far } from '@endo/far'; +import { E } from '@endo/far'; /// /** * @import {Installation, Instance} from '@agoric/zoe/src/zoeService/utils.js'; */ -const trace = makeTracer('FixBuggySA', true); +const trace = makeTracer('UpgradeSA', true); /** * @import {start as StartFn} from '@agoric/orchestration/src/examples/send-anywhere.contract.js'; @@ -30,7 +30,7 @@ const trace = makeTracer('FixBuggySA', true); * }} powers * @param {...any} rest */ -export const fixSendAnywhere = async ( +export const upgradeSendAnywhere = async ( { consume: { agoricNames, @@ -45,7 +45,7 @@ export const fixSendAnywhere = async ( }, { options: { sendAnywhereRef } }, ) => { - trace(fixSendAnywhere.name); + trace(upgradeSendAnywhere.name); const saInstance = await instances.consume.sendAnywhere; trace('saInstance', saInstance); @@ -53,28 +53,24 @@ export const fixSendAnywhere = async ( const marshaller = await E(board).getReadonlyMarshaller(); - // This apparently pointless wrapper is to maintain structural parity - // with the buggy core-eval's wrapper to make lookup() hang. - const agoricNamesResolves = Far('agoricNames that resolves', { - lookup: async (...args) => { - return E(agoricNames).lookup(...args); - }, - }); - const privateArgs = await deeplyFulfilledObject( harden({ - agoricNames: agoricNamesResolves, + agoricNames, localchain, marshaller, orchestrationService: cosmosInterchainService, storageNode: E(NonNullish(await chainStorage)).makeChildNode( - 'sendAnywhere', + 'send-anywhere', ), timerService: chainTimerService, + // undefined so `registerKnownChainsAndAssets` does not run again + chainInfo: undefined, + assetInfo: undefined, }), ); trace('upgrading...'); + trace('ref', sendAnywhereRef); await E(saKit.adminFacet).upgradeContract( sendAnywhereRef.bundleID, privateArgs, @@ -82,13 +78,13 @@ export const fixSendAnywhere = async ( trace('done'); }; -harden(fixSendAnywhere); +harden(upgradeSendAnywhere); export const getManifestForValueVow = ({ restoreRef }, { sendAnywhereRef }) => { console.log('sendAnywhereRef', sendAnywhereRef); return { manifest: { - [fixSendAnywhere.name]: { + [upgradeSendAnywhere.name]: { consume: { agoricNames: true, board: true, @@ -120,7 +116,7 @@ export const getManifestForValueVow = ({ restoreRef }, { sendAnywhereRef }) => { export const defaultProposalBuilder = async ({ publishRef, install }) => harden({ // Somewhat unorthodox, source the exports from this builder module - sourceSpec: '@agoric/builders/scripts/testing/fix-buggy-sendAnywhere.js', + sourceSpec: '@agoric/builders/scripts/testing/upgrade-send-anywhere.js', getManifestCall: [ 'getManifestForValueVow', { @@ -139,5 +135,5 @@ export default async (homeP, endowments) => { const dspModule = await import('@agoric/deploy-script-support'); const { makeHelpers } = dspModule; const { writeCoreEval } = await makeHelpers(homeP, endowments); - await writeCoreEval(fixSendAnywhere.name, defaultProposalBuilder); + await writeCoreEval(upgradeSendAnywhere.name, defaultProposalBuilder); }; diff --git a/packages/builders/test/snapshots/orchestration-imports.test.js.md b/packages/builders/test/snapshots/orchestration-imports.test.js.md index 879f4029b18..247866d9e3b 100644 --- a/packages/builders/test/snapshots/orchestration-imports.test.js.md +++ b/packages/builders/test/snapshots/orchestration-imports.test.js.md @@ -318,6 +318,20 @@ Generated by [AVA](https://avajs.dev). ], }, }, + ForwardOptsShape: Object @match:splitRecord { + payload: [ + {}, + { + retries: Object @match:kind { + payload: 'number', + }, + timeout: Object @match:string { + payload: [], + }, + }, + {}, + ], + }, IBCChannelIDShape: Object @match:string { payload: [], }, @@ -405,6 +419,20 @@ Generated by [AVA](https://avajs.dev). payload: [ {}, { + forwardOpts: Object @match:splitRecord { + payload: [ + {}, + { + retries: Object @match:kind { + payload: 'number', + }, + timeout: Object @match:string { + payload: [], + }, + }, + {}, + ], + }, memo: Object @match:string { payload: [], }, diff --git a/packages/builders/test/snapshots/orchestration-imports.test.js.snap b/packages/builders/test/snapshots/orchestration-imports.test.js.snap index dba58251741..f1588bc482d 100644 Binary files a/packages/builders/test/snapshots/orchestration-imports.test.js.snap and b/packages/builders/test/snapshots/orchestration-imports.test.js.snap differ diff --git a/packages/fast-usdc/test/fast-usdc.contract.test.ts b/packages/fast-usdc/test/fast-usdc.contract.test.ts index 7ec53744173..94e79dddbb8 100644 --- a/packages/fast-usdc/test/fast-usdc.contract.test.ts +++ b/packages/fast-usdc/test/fast-usdc.contract.test.ts @@ -404,10 +404,14 @@ const makeCustomer = ( const myMsg = local.find(lm => { if (lm.type !== 'VLOCALCHAIN_EXECUTE_TX') return false; const [ibcTransferMsg] = lm.messages; + // support advances to noble + other chains + const receiver = + ibcTransferMsg.receiver === 'pfm' + ? JSON.parse(ibcTransferMsg.memo).forward.receiver + : ibcTransferMsg.receiver; return ( ibcTransferMsg['@type'] === - '/ibc.applications.transfer.v1.MsgTransfer' && - ibcTransferMsg.receiver === EUD + '/ibc.applications.transfer.v1.MsgTransfer' && receiver === EUD ); }); if (!myMsg) { @@ -423,17 +427,25 @@ const makeCustomer = ( { amount: String(toReceive.value), denom: uusdcOnAgoric }, 'C4', ); - t.log(who, 'sees', ibcTransferMsg.token, 'sent to', EUD); - // TODO #10445 expect PFM memo - t.is(ibcTransferMsg.memo, '', 'TODO expecting PFM memo'); - - // TODO #10445 expect routing through noble, not osmosis + if (!EUD.startsWith('noble')) { + t.like( + JSON.parse(ibcTransferMsg.memo), + { + forward: { + receiver: EUD, + }, + }, + 'PFM receiver is EUD', + ); + } else { + t.like(ibcTransferMsg, { receiver: EUD }); + } t.is( ibcTransferMsg.sourceChannel, - fetchedChainInfo.agoric.connections['osmosis-1'].transferChannel + fetchedChainInfo.agoric.connections['noble-1'].transferChannel .channelId, - 'TODO expecting routing through Noble', + 'expect routing through Noble', ); }, }); diff --git a/packages/fast-usdc/test/snapshots/fast-usdc.contract.test.ts.md b/packages/fast-usdc/test/snapshots/fast-usdc.contract.test.ts.md index ece2a6e83cd..72890a09ece 100644 --- a/packages/fast-usdc/test/snapshots/fast-usdc.contract.test.ts.md +++ b/packages/fast-usdc/test/snapshots/fast-usdc.contract.test.ts.md @@ -43,6 +43,7 @@ Generated by [AVA](https://avajs.dev). bech32Prefix: 'agoric', chainId: 'agoric-3', icqEnabled: false, + pfmEnabled: true, stakingTokens: [ { denom: 'ubld', @@ -53,11 +54,13 @@ Generated by [AVA](https://avajs.dev). bech32Prefix: 'noble', chainId: 'noble-1', icqEnabled: false, + pfmEnabled: true, }, osmosis: { bech32Prefix: 'osmo', chainId: 'osmosis-1', icqEnabled: true, + pfmEnabled: true, stakingTokens: [ { denom: 'uosmo', diff --git a/packages/fast-usdc/test/snapshots/fast-usdc.contract.test.ts.snap b/packages/fast-usdc/test/snapshots/fast-usdc.contract.test.ts.snap index b4f6db225e5..c289cbdd0bb 100644 Binary files a/packages/fast-usdc/test/snapshots/fast-usdc.contract.test.ts.snap and b/packages/fast-usdc/test/snapshots/fast-usdc.contract.test.ts.snap differ diff --git a/packages/fast-usdc/test/supports.ts b/packages/fast-usdc/test/supports.ts index 2c9154ab227..7a64bcf2ff2 100644 --- a/packages/fast-usdc/test/supports.ts +++ b/packages/fast-usdc/test/supports.ts @@ -4,6 +4,7 @@ import { makeFakeStorageKit } from '@agoric/internal/src/storage-test-utils.js'; import { eventLoopIteration } from '@agoric/internal/src/testing-utils.js'; import { denomHash, + withChainCapabilities, type CosmosChainInfo, type Denom, } from '@agoric/orchestration'; @@ -196,7 +197,7 @@ export const commonSetup = async (t: ExecutionContext) => { ); const chainInfo = harden(() => { - const { agoric, osmosis, noble } = fetchedChainInfo; + const { agoric, osmosis, noble } = withChainCapabilities(fetchedChainInfo); return { agoric, osmosis, noble }; })(); diff --git a/packages/orchestration/src/cosmos-api.ts b/packages/orchestration/src/cosmos-api.ts index cbbe4658760..661d92a23d5 100644 --- a/packages/orchestration/src/cosmos-api.ts +++ b/packages/orchestration/src/cosmos-api.ts @@ -333,6 +333,10 @@ export interface IBCMsgTransferOptions { timeoutHeight?: MsgTransfer['timeoutHeight']; timeoutTimestamp?: MsgTransfer['timeoutTimestamp']; memo?: string; + forwardOpts?: { + timeout?: ForwardInfo['forward']['timeout']; + retries?: ForwardInfo['forward']['retries']; + }; } /** @@ -397,5 +401,6 @@ export type TransferRoute = { } | { receiver: string; + forwardInfo?: never; } ); diff --git a/packages/orchestration/src/examples/auto-stake-it.contract.js b/packages/orchestration/src/examples/auto-stake-it.contract.js index d08781e47ff..4db3c55adcb 100644 --- a/packages/orchestration/src/examples/auto-stake-it.contract.js +++ b/packages/orchestration/src/examples/auto-stake-it.contract.js @@ -8,10 +8,12 @@ import { preparePortfolioHolder } from '../exos/portfolio-holder-kit.js'; import { withOrchestration } from '../utils/start-helper.js'; import { prepareStakingTap } from './auto-stake-it-tap-kit.js'; import * as flows from './auto-stake-it.flows.js'; +import { registerChainsAndAssets } from '../utils/chain-hub-helper.js'; /** * @import {Zone} from '@agoric/zone'; * @import {OrchestrationPowers, OrchestrationTools} from '../utils/start-helper.js'; + * @import {CosmosChainInfo, Denom, DenomDetail} from '../types.js'; */ /** @@ -23,13 +25,15 @@ import * as flows from './auto-stake-it.flows.js'; * @param {ZCF} zcf * @param {OrchestrationPowers & { * marshaller: Marshaller; - * }} _privateArgs + * chainInfo?: Record; + * assetInfo?: [Denom, DenomDetail & { brandKey?: string }][]; + * }} privateArgs * @param {Zone} zone * @param {OrchestrationTools} tools */ const contract = async ( zcf, - _privateArgs, + privateArgs, zone, { chainHub, orchestrateAll, vowTools }, ) => { @@ -67,6 +71,13 @@ const contract = async ( const creatorFacet = prepareChainHubAdmin(zone, chainHub); + registerChainsAndAssets( + chainHub, + zcf.getTerms().brands, + privateArgs.chainInfo, + privateArgs.assetInfo, + ); + return { publicFacet, creatorFacet }; }; diff --git a/packages/orchestration/src/examples/basic-flows.contract.js b/packages/orchestration/src/examples/basic-flows.contract.js index 60f58cadd1f..ab8387d2ec8 100644 --- a/packages/orchestration/src/examples/basic-flows.contract.js +++ b/packages/orchestration/src/examples/basic-flows.contract.js @@ -6,10 +6,12 @@ import { InvitationShape } from '@agoric/zoe/src/typeGuards.js'; import { M } from '@endo/patterns'; import { preparePortfolioHolder } from '../exos/portfolio-holder-kit.js'; import { withOrchestration } from '../utils/start-helper.js'; +import { registerChainsAndAssets } from '../utils/chain-hub-helper.js'; import * as flows from './basic-flows.flows.js'; /** * @import {Zone} from '@agoric/zone'; + * @import {CosmosChainInfo, Denom, DenomDetail} from '@agoric/orchestration'; * @import {OrchestrationPowers, OrchestrationTools} from '../utils/start-helper.js'; */ @@ -17,15 +19,17 @@ import * as flows from './basic-flows.flows.js'; * @param {ZCF} zcf * @param {OrchestrationPowers & { * marshaller: Marshaller; - * }} _privateArgs + * chainInfo?: Record; + * assetInfo?: [Denom, DenomDetail & { brandKey?: string }][]; + * }} privateArgs * @param {Zone} zone * @param {OrchestrationTools} tools */ const contract = async ( zcf, - _privateArgs, + privateArgs, zone, - { orchestrateAll, vowTools }, + { chainHub, orchestrateAll, vowTools }, ) => { const makePortfolioHolder = preparePortfolioHolder( zone.subZone('portfolio'), @@ -56,6 +60,13 @@ const contract = async ( }, ); + registerChainsAndAssets( + chainHub, + zcf.getTerms().brands, + privateArgs.chainInfo, + privateArgs.assetInfo, + ); + return { publicFacet }; }; diff --git a/packages/orchestration/src/examples/staking-combinations.contract.js b/packages/orchestration/src/examples/staking-combinations.contract.js index b38da6633d6..209fe7dc787 100644 --- a/packages/orchestration/src/examples/staking-combinations.contract.js +++ b/packages/orchestration/src/examples/staking-combinations.contract.js @@ -131,6 +131,7 @@ const contract = async ( ); const orchFns = orchestrateAll(flows, { + chainHub, sharedLocalAccountP, makeCombineInvitationMakers, makeExtraInvitationMaker, diff --git a/packages/orchestration/src/examples/staking-combinations.flows.js b/packages/orchestration/src/examples/staking-combinations.flows.js index 23b1583ce58..794d13099f8 100644 --- a/packages/orchestration/src/examples/staking-combinations.flows.js +++ b/packages/orchestration/src/examples/staking-combinations.flows.js @@ -1,6 +1,6 @@ /** * @import {GuestInterface} from '@agoric/async-flow'; - * @import {Orchestrator, OrchestrationFlow, AmountArg, CosmosValidatorAddress, ChainAddress, LocalAccountMethods, OrchestrationAccountI} from '../types.js' + * @import {Orchestrator, OrchestrationFlow, AmountArg, CosmosValidatorAddress, ChainAddress, LocalAccountMethods, OrchestrationAccountI, ChainHub} from '../types.js' * @import {ContinuingOfferResult, InvitationMakers} from '@agoric/smart-wallet/src/types.js'; * @import {LocalOrchestrationAccountKit} from '../exos/local-orchestration-account.js'; * @import {MakeCombineInvitationMakers} from '../exos/combine-invitation-makers.js'; @@ -9,7 +9,7 @@ */ import { mustMatch } from '@endo/patterns'; -import { makeError, q } from '@endo/errors'; +import { Fail, makeError, q } from '@endo/errors'; import { makeTracer } from '@agoric/internal'; import { ChainAddressShape } from '../typeGuards.js'; @@ -48,6 +48,7 @@ harden(makeAccount); * @satisfies {OrchestrationFlow} * @param {Orchestrator} orch * @param {object} ctx + * @param {GuestInterface} ctx.chainHub * @param {Promise>} ctx.sharedLocalAccountP * @param {GuestInterface} ctx.zoeTools * @param {GuestInterface} account @@ -57,7 +58,7 @@ harden(makeAccount); */ export const depositAndDelegate = async ( orch, - { sharedLocalAccountP, zoeTools }, + { chainHub, sharedLocalAccountP, zoeTools }, account, seat, validator, @@ -84,7 +85,11 @@ export const depositAndDelegate = async ( throw errMsg; } seat.exit(); - await account.delegate(validator, give.Stake); + const denom = chainHub.getDenom(give.Stake.brand); + if (!denom) throw Fail`unknown brand ${q(give.Stake.brand)}`; + // TODO #10449 amountToCoin accepts brands + const denomAmount = harden({ denom, value: give.Stake.value }); + await account.delegate(validator, denomAmount); }; harden(depositAndDelegate); diff --git a/packages/orchestration/src/examples/swap.contract.js b/packages/orchestration/src/examples/swap.contract.js index e726d371cf1..400eae50b92 100644 --- a/packages/orchestration/src/examples/swap.contract.js +++ b/packages/orchestration/src/examples/swap.contract.js @@ -3,6 +3,7 @@ import { TimerServiceShape } from '@agoric/time'; import { M } from '@endo/patterns'; import { withOrchestration } from '../utils/start-helper.js'; import * as flows from './swap.flows.js'; +import { CosmosChainInfoShape, DenomDetailShape } from '../typeGuards.js'; /** * @import {TimerService} from '@agoric/time'; @@ -12,6 +13,7 @@ import * as flows from './swap.flows.js'; * @import {NameHub} from '@agoric/vats'; * @import {Zone} from '@agoric/zone'; * @import {OrchestrationTools} from '../utils/start-helper.js'; + * @import {CosmosChainInfo, Denom, DenomDetail} from '@agoric/orchestration'; */ /** @type {ContractMeta} */ @@ -23,6 +25,8 @@ export const meta = { storageNode: StorageNodeShape, marshaller: M.remotable('marshaller'), timerService: M.or(TimerServiceShape, null), + chainInfo: M.recordOf(M.string(), CosmosChainInfoShape), + assetInfo: M.arrayOf([M.string(), DenomDetailShape]), }, upgradability: 'canUpgrade', }; @@ -49,6 +53,8 @@ harden(makeNatAmountShape); * storageNode: Remote; * timerService: Remote; * marshaller: Marshaller; + * chainInfo: Record; + * assetInfo: [Denom, DenomDetail & { brandKey?: string }][]; * }} privateArgs * @param {Zone} zone * @param {OrchestrationTools} tools diff --git a/packages/orchestration/src/exos/chain-hub.js b/packages/orchestration/src/exos/chain-hub.js index 6a300fade5a..1477ca91de8 100644 --- a/packages/orchestration/src/exos/chain-hub.js +++ b/packages/orchestration/src/exos/chain-hub.js @@ -11,6 +11,7 @@ import { DenomAmountShape, DenomDetailShape, ForwardInfoShape, + ForwardOptsShape, IBCChannelIDShape, IBCConnectionInfoShape, } from '../typeGuards.js'; @@ -20,7 +21,7 @@ import { getBech32Prefix } from '../utils/address.js'; * @import {NameHub} from '@agoric/vats'; * @import {Vow, VowTools} from '@agoric/vow'; * @import {Zone} from '@agoric/zone'; - * @import {CosmosAssetInfo, CosmosChainInfo, ForwardInfo, IBCConnectionInfo, TransferRoute} from '../cosmos-api.js'; + * @import {CosmosAssetInfo, CosmosChainInfo, ForwardInfo, IBCConnectionInfo, IBCMsgTransferOptions, TransferRoute} from '../cosmos-api.js'; * @import {ChainInfo, KnownChains} from '../chain-info.js'; * @import {ChainAddress, Denom, DenomAmount} from '../orchestration-api.js'; * @import {Remote, TypedPattern} from '@agoric/internal'; @@ -211,10 +212,7 @@ const ChainHubI = M.interface('ChainHub', { getDenom: M.call(BrandShape).returns(M.or(M.string(), M.undefined())), makeChainAddress: M.call(M.string()).returns(ChainAddressShape), makeTransferRoute: M.call(ChainAddressShape, DenomAmountShape, M.string()) - .optional({ - timeout: M.string(), - retries: M.number(), - }) + .optional(ForwardOptsShape) .returns(M.or(M.undefined(), TransferRouteShape)), }); @@ -515,7 +513,7 @@ export const makeChainHub = (zone, agoricNames, vowTools) => { * @param {ChainAddress} destination * @param {DenomAmount} denomAmount * @param {string} srcChainName - * @param {Pick} [forwardOpts] + * @param {IBCMsgTransferOptions['forwardOpts']} [forwardOpts] * @returns {TransferRoute} single hop, multi hop * @throws {Error} if unable to determine route */ @@ -546,7 +544,7 @@ export const makeChainHub = (zone, agoricNames, vowTools) => { // TODO use getConnectionInfo once its sync const connKey = connectionKey(holdingChainId, destination.chainId); connectionInfos.has(connKey) || - Fail`no connection info found for ${q(connKey)}`; + Fail`no connection info found for ${holdingChainId}<->${destination.chainId}`; const { transferChannel } = denormalizeConnectionInfo( holdingChainId, // from chain (primary) @@ -570,11 +568,11 @@ export const makeChainHub = (zone, agoricNames, vowTools) => { // TODO use getConnectionInfo once its sync const currToIssuerKey = connectionKey(holdingChainId, baseChainId); connectionInfos.has(currToIssuerKey) || - Fail`no connection info found for ${q(currToIssuerKey)}`; + Fail`no connection info found for ${holdingChainId}<->${baseChainId}`; const issuerToDestKey = connectionKey(baseChainId, destination.chainId); connectionInfos.has(issuerToDestKey) || - Fail`no connection info found for ${q(issuerToDestKey)}`; + Fail`no connection info found for ${baseChainId}<->${destination.chainId}`; const currToIssuer = denormalizeConnectionInfo( holdingChainId, diff --git a/packages/orchestration/src/exos/cosmos-orchestration-account.js b/packages/orchestration/src/exos/cosmos-orchestration-account.js index cc33d39c448..843bb10fbb5 100644 --- a/packages/orchestration/src/exos/cosmos-orchestration-account.js +++ b/packages/orchestration/src/exos/cosmos-orchestration-account.js @@ -340,6 +340,8 @@ export const prepareCosmosOrchestrationAccountKit = ( * @returns {Coin} */ amountToCoin(amount) { + !('brand' in amount) || + Fail`'amountToCoin' not working for ${q(amount.brand)} until #10449; use 'DenomAmount' for now`; return coerceCoin(chainHub, amount); }, }, diff --git a/packages/orchestration/src/exos/local-orchestration-account.js b/packages/orchestration/src/exos/local-orchestration-account.js index 7ab823a0719..20285c1c9b2 100644 --- a/packages/orchestration/src/exos/local-orchestration-account.js +++ b/packages/orchestration/src/exos/local-orchestration-account.js @@ -11,7 +11,6 @@ import { Fail, q } from '@endo/errors'; import { AmountArgShape, AnyNatAmountsRecord, - ChainAddressShape, DenomAmountShape, DenomShape, IBCTransferOptionsShape, @@ -24,11 +23,12 @@ import { makeTimestampHelper } from '../utils/time.js'; import { preparePacketTools } from './packet-tools.js'; import { prepareIBCTools } from './ibc-packet.js'; import { coerceCoin, coerceDenomAmount } from '../utils/amounts.js'; +import { TransferRouteShape } from './chain-hub.js'; /** * @import {HostOf} from '@agoric/async-flow'; * @import {LocalChain, LocalChainAccount} from '@agoric/vats/src/localchain.js'; - * @import {AmountArg, ChainAddress, DenomAmount, IBCMsgTransferOptions, IBCConnectionInfo, OrchestrationAccountI, LocalAccountMethods} from '@agoric/orchestration'; + * @import {AmountArg, ChainAddress, DenomAmount, IBCMsgTransferOptions, IBCConnectionInfo, OrchestrationAccountI, LocalAccountMethods, TransferRoute} from '@agoric/orchestration'; * @import {RecorderKit, MakeRecorderKit} from '@agoric/zoe/src/contractSupport/recorder.js'. * @import {Zone} from '@agoric/zone'; * @import {Remote} from '@agoric/internal'; @@ -107,7 +107,7 @@ export const prepareLocalOrchestrationAccountKit = ( zoeTools, }, ) => { - const { watch, allVows, asVow, when } = vowTools; + const { watch, asVow, when } = vowTools; const { makeIBCTransferSender } = prepareIBCTools( zone.subZone('ibcTools'), vowTools, @@ -134,11 +134,10 @@ export const prepareLocalOrchestrationAccountKit = ( .returns(VowShape), }), transferWatcher: M.interface('transferWatcher', { - onFulfilled: M.call([M.record(), M.nat()]) + onFulfilled: M.call(M.nat()) .optional({ - destination: ChainAddressShape, opts: M.or(M.undefined(), IBCTransferOptionsShape), - amount: DenomAmountShape, + route: TransferRouteShape, }) .returns(Vow$(M.record())), }), @@ -345,37 +344,34 @@ export const prepareLocalOrchestrationAccountKit = ( }, transferWatcher: { /** - * @param {[ - * { transferChannel: IBCConnectionInfo['transferChannel'] }, - * bigint, - * ]} results + * @param {bigint} timeoutTimestamp * @param {{ - * destination: ChainAddress; - * opts?: IBCMsgTransferOptions; - * amount: DenomAmount; + * opts?: Omit; + * route: TransferRoute; * }} ctx */ - onFulfilled( - [{ transferChannel }, timeoutTimestamp], - { opts, amount, destination }, - ) { + onFulfilled(timeoutTimestamp, { opts, route }) { + const { forwardInfo, ...transferDetails } = route; + /** @type {string | undefined} */ + let memo; + if (opts && 'memo' in opts) { + memo = opts.memo; + } + if (forwardInfo) { + // forward memo takes precedence + memo = JSON.stringify(forwardInfo); + } const transferMsg = typedJson( '/ibc.applications.transfer.v1.MsgTransfer', { - sourcePort: transferChannel.portId, - sourceChannel: transferChannel.channelId, - token: { - amount: String(amount.value), - denom: amount.denom, - }, + ...transferDetails, sender: this.state.address.value, - receiver: destination.value, timeoutHeight: opts?.timeoutHeight ?? { revisionHeight: 0n, revisionNumber: 0n, }, timeoutTimestamp, - memo: opts?.memo ?? '', + memo: memo ?? '', }, ); @@ -395,9 +391,7 @@ export const prepareLocalOrchestrationAccountKit = ( * first result */ extractFirstResultWatcher: { - /** - * @param {Record[]} results - */ + /** @param {Record[]} results */ onFulfilled(results) { results.length === 1 || Fail`expected exactly one result; got ${results}`; @@ -504,9 +498,7 @@ export const prepareLocalOrchestrationAccountKit = ( }); }); }, - /** - * @type {HostOf} - */ + /** @type {HostOf} */ getBalance(denomArg) { return asVow(() => { const [brand, denom] = @@ -549,9 +541,7 @@ export const prepareLocalOrchestrationAccountKit = ( ); }, - /** - * @type {HostOf} - */ + /** @type {HostOf} */ getPublicTopics() { // getStoragePath resolves promptly (same run), so we don't need a watcher // eslint-disable-next-line no-restricted-syntax @@ -682,16 +672,23 @@ export const prepareLocalOrchestrationAccountKit = ( * timeoutTimestamp are not supplied, a default timeoutTimestamp will * be set for 5 minutes in the future * @returns {Vow} + * @throws {Error} if route is not determinable, asset is not + * recognized, or the transfer is rejected (insufficient funds, + * timeout) */ transfer(destination, amount, opts) { return asVow(() => { trace('Transferring funds from LCA over IBC'); + const denomAmount = coerceDenomAmount(chainHub, amount); - const connectionInfoV = watch( - chainHub.getConnectionInfo( - this.state.address.chainId, - destination.chainId, - ), + const { forwardOpts, ...rest } = opts ?? {}; + + // throws if route is not determinable + const route = chainHub.makeTransferRoute( + destination, + denomAmount, + 'agoric', + forwardOpts, ); // set a `timeoutTimestamp` if caller does not supply either `timeoutHeight` or `timeoutTimestamp` @@ -700,17 +697,16 @@ export const prepareLocalOrchestrationAccountKit = ( opts?.timeoutTimestamp ?? (opts?.timeoutHeight ? 0n - : E(timestampHelper).getTimeoutTimestampNS()); + : asVow(() => E(timestampHelper).getTimeoutTimestampNS())); // don't resolve the vow until the transfer is confirmed on remote // and reject vow if the transfer fails for any reason const resultV = watch( - allVows([connectionInfoV, timeoutTimestampVowOrValue]), + timeoutTimestampVowOrValue, this.facets.transferWatcher, { - opts, - amount: coerceDenomAmount(chainHub, amount), - destination, + opts: rest, + route, }, ); return resultV; diff --git a/packages/orchestration/src/orchestration-api.ts b/packages/orchestration/src/orchestration-api.ts index 2294f376dce..cb97cadcc56 100644 --- a/packages/orchestration/src/orchestration-api.ts +++ b/packages/orchestration/src/orchestration-api.ts @@ -195,8 +195,8 @@ export interface OrchestrationAccountI { * @param destination - the account to transfer the amount to. * @param [opts] - an optional memo to include with the transfer, which could drive custom PFM behavior, and timeout parameters * @returns void - * - * TODO document the mapping from the address to the destination chain. + * @throws {Error} if route is not determinable, asset is not recognized, or + * the transfer is rejected (insufficient funds, timeout) */ transfer: ( destination: ChainAddress, diff --git a/packages/builders/scripts/testing/start-auto-stake-it.js b/packages/orchestration/src/proposals/start-auto-stake-it.js similarity index 55% rename from packages/builders/scripts/testing/start-auto-stake-it.js rename to packages/orchestration/src/proposals/start-auto-stake-it.js index 8a02140c012..69cfc7721e5 100644 --- a/packages/builders/scripts/testing/start-auto-stake-it.js +++ b/packages/orchestration/src/proposals/start-auto-stake-it.js @@ -11,6 +11,7 @@ import { deeplyFulfilled } from '@endo/marshal'; /** * @import {AutoStakeItSF} from '@agoric/orchestration/src/examples/auto-stake-it.contract.js'; + * @import {CosmosChainInfo, Denom, DenomDetail} from '@agoric/orchestration'; */ const contractName = 'autoAutoStakeIt'; @@ -18,26 +19,35 @@ const trace = makeTracer(contractName, true); /** * @param {BootstrapPowers} powers + * @param {{ + * options: { + * chainInfo: Record; + * assetInfo: [Denom, DenomDetail & { brandKey?: string }][]; + * }; + * }} config */ -export const startAutoStakeIt = async ({ - consume: { - agoricNames, - board, - chainStorage, - chainTimerService, - cosmosInterchainService, - localchain, - startUpgradable, - }, - installation: { - // @ts-expect-error not a WellKnownName - consume: { [contractName]: installation }, - }, - instance: { - // @ts-expect-error not a WellKnownName - produce: { [contractName]: produceInstance }, +export const startAutoStakeIt = async ( + { + consume: { + agoricNames, + board, + chainStorage, + chainTimerService, + cosmosInterchainService, + localchain, + startUpgradable, + }, + installation: { + // @ts-expect-error not a WellKnownName + consume: { [contractName]: installation }, + }, + instance: { + // @ts-expect-error not a WellKnownName + produce: { [contractName]: produceInstance }, + }, }, -}) => { + { options: { chainInfo, assetInfo } }, +) => { trace(`start ${contractName}`); await null; @@ -58,6 +68,8 @@ export const startAutoStakeIt = async ({ storageNode, marshaller, timerService: chainTimerService, + chainInfo, + assetInfo, }), ), }; @@ -67,10 +79,7 @@ export const startAutoStakeIt = async ({ }; harden(startAutoStakeIt); -export const getManifestForContract = ( - { restoreRef }, - { installKeys, ...options }, -) => { +export const getManifest = ({ restoreRef }, { installKeys, options }) => { return { manifest: { [startAutoStakeIt.name]: { @@ -97,32 +106,3 @@ export const getManifestForContract = ( options, }; }; - -/** @type {import('@agoric/deploy-script-support/src/externalTypes.js').CoreEvalBuilder} */ -export const defaultProposalBuilder = async ({ publishRef, install }) => { - return harden({ - // Somewhat unorthodox, source the exports from this builder module - sourceSpec: '@agoric/builders/scripts/testing/start-auto-stake-it.js', - getManifestCall: [ - 'getManifestForContract', - { - installKeys: { - autoAutoStakeIt: publishRef( - install( - '@agoric/orchestration/src/examples/auto-stake-it.contract.js', - ), - ), - }, - }, - ], - }); -}; - -/** @type {import('@agoric/deploy-script-support/src/externalTypes.js').DeployScriptFunction} */ -export default async (homeP, endowments) => { - // import dynamically so the module can work in CoreEval environment - const dspModule = await import('@agoric/deploy-script-support'); - const { makeHelpers } = dspModule; - const { writeCoreEval } = await makeHelpers(homeP, endowments); - await writeCoreEval(startAutoStakeIt.name, defaultProposalBuilder); -}; diff --git a/packages/orchestration/src/proposals/start-basic-flows.js b/packages/orchestration/src/proposals/start-basic-flows.js index e19b242ff90..59682e21e10 100644 --- a/packages/orchestration/src/proposals/start-basic-flows.js +++ b/packages/orchestration/src/proposals/start-basic-flows.js @@ -6,6 +6,7 @@ import { makeStorageNodeChild } from '@agoric/internal/src/lib-chainStorage.js'; import { E } from '@endo/far'; /** + * @import {CosmosChainInfo, Denom, DenomDetail} from '@agoric/orchestration'; * @import {BasicFlowsSF} from '../examples/basic-flows.contract.js'; */ @@ -15,30 +16,59 @@ const contractName = 'basicFlows'; /** * See `@agoric/builders/builders/scripts/orchestration/init-basic-flows.js` for * the accompanying proposal builder. Run `agoric run - * packages/builders/scripts/orchestration/init-basic-flows.js` to build the + * packages/builders/scripts/orchestration/init-basic-flows.js --chainInfo + * 'chainName:CosmosChainInfo' --assetInfo 'denom:DenomDetail'` to build the * contract and proposal files. * - * @param {BootstrapPowers} powers + * @param {BootstrapPowers & { + * installation: { + * consume: { + * basicFlows: Installation; + * }; + * }; + * instance: { + * produce: { + * basicFlows: Producer; + * }; + * }; + * issuer: { + * consume: { + * BLD: Issuer<'nat'>; + * IST: Issuer<'nat'>; + * USDC: Issuer<'nat'>; + * }; + * }; + * }} powers + * @param {{ + * options: { + * chainInfo: Record; + * assetInfo: [Denom, DenomDetail & { brandKey?: string }][]; + * }; + * }} config */ -export const startBasicFlows = async ({ - consume: { - agoricNames, - board, - chainStorage, - chainTimerService, - cosmosInterchainService, - localchain, - startUpgradable, - }, - installation: { - // @ts-expect-error not a WellKnownName - consume: { [contractName]: installation }, - }, - instance: { - // @ts-expect-error not a WellKnownName - produce: { [contractName]: produceInstance }, +export const startBasicFlows = async ( + { + consume: { + agoricNames, + board, + chainStorage, + chainTimerService, + cosmosInterchainService, + localchain, + startUpgradable, + }, + installation: { + consume: { [contractName]: installation }, + }, + instance: { + produce: { [contractName]: produceInstance }, + }, + issuer: { + consume: { BLD, IST }, + }, }, -}) => { + { options: { chainInfo, assetInfo } }, +) => { trace(`start ${contractName}`); await null; @@ -49,6 +79,10 @@ export const startBasicFlows = async ({ const startOpts = { label: 'basicFlows', installation, + issuerKeywordRecord: { + BLD: await BLD, + IST: await IST, + }, terms: undefined, privateArgs: { agoricNames: await agoricNames, @@ -57,6 +91,8 @@ export const startBasicFlows = async ({ storageNode, marshaller, timerService: await chainTimerService, + chainInfo, + assetInfo, }, }; @@ -67,7 +103,7 @@ harden(startBasicFlows); export const getManifestForContract = ( { restoreRef }, - { installKeys, ...options }, + { installKeys, options }, ) => { return { manifest: { @@ -87,6 +123,9 @@ export const getManifestForContract = ( instance: { produce: { [contractName]: true }, }, + issuer: { + consume: { BLD: true, IST: true }, + }, }, }, installations: { diff --git a/packages/orchestration/src/proposals/start-send-anywhere.js b/packages/orchestration/src/proposals/start-send-anywhere.js index 6d1c6932bc1..b0db84c3f32 100644 --- a/packages/orchestration/src/proposals/start-send-anywhere.js +++ b/packages/orchestration/src/proposals/start-send-anywhere.js @@ -6,17 +6,15 @@ import { import { E } from '@endo/far'; /// + /** * @import {Installation} from '@agoric/zoe/src/zoeService/utils.js'; * @import {CosmosChainInfo, Denom, DenomDetail} from '@agoric/orchestration'; + * @import {start as StartFn} from '@agoric/orchestration/src/examples/send-anywhere.contract.js'; */ const trace = makeTracer('StartSA', true); -/** - * @import {start as StartFn} from '@agoric/orchestration/src/examples/send-anywhere.contract.js'; - */ - /** * @param {BootstrapPowers & { * installation: { @@ -24,6 +22,18 @@ const trace = makeTracer('StartSA', true); * sendAnywhere: Installation; * }; * }; + * instance: { + * produce: { + * sendAnywhere: Producer; + * }; + * }; + * issuer: { + * consume: { + * BLD: Issuer<'nat'>; + * IST: Issuer<'nat'>; + * USDC: Issuer<'nat'>; + * }; + * }; * }} powers * @param {{ * options: { @@ -47,11 +57,10 @@ export const startSendAnywhere = async ( consume: { sendAnywhere }, }, instance: { - // @ts-expect-error unknown instance produce: { sendAnywhere: produceInstance }, }, issuer: { - consume: { IST }, + consume: { BLD, IST }, }, }, { options: { chainInfo, assetInfo } }, @@ -78,7 +87,10 @@ export const startSendAnywhere = async ( const { instance } = await E(startUpgradable)({ label: 'send-anywhere', installation: sendAnywhere, - issuerKeywordRecord: { Stable: await IST }, + issuerKeywordRecord: { + Stable: await IST, + Stake: await BLD, + }, privateArgs, }); produceInstance.resolve(instance); @@ -107,7 +119,7 @@ export const getManifest = ({ restoreRef }, { installationRef, options }) => { produce: { sendAnywhere: true }, }, issuer: { - consume: { IST: true }, + consume: { BLD: true, IST: true }, }, }, }, diff --git a/packages/orchestration/src/typeGuards.js b/packages/orchestration/src/typeGuards.js index f97f2bb24c0..4496e245643 100644 --- a/packages/orchestration/src/typeGuards.js +++ b/packages/orchestration/src/typeGuards.js @@ -4,7 +4,7 @@ import { M } from '@endo/patterns'; /** * @import {TypedPattern} from '@agoric/internal'; - * @import {ChainAddress, CosmosAssetInfo, Chain, ChainInfo, CosmosChainInfo, DenomAmount, DenomInfo, AmountArg, CosmosValidatorAddress, OrchestrationPowers, ForwardInfo} from './types.js'; + * @import {ChainAddress, CosmosAssetInfo, Chain, ChainInfo, CosmosChainInfo, DenomAmount, DenomInfo, AmountArg, CosmosValidatorAddress, OrchestrationPowers, ForwardInfo, IBCMsgTransferOptions} from './types.js'; * @import {Any as Proto3Msg} from '@agoric/cosmic-proto/google/protobuf/any.js'; * @import {TxBody} from '@agoric/cosmic-proto/cosmos/tx/v1beta1/tx.js'; * @import {Coin} from '@agoric/cosmic-proto/cosmos/base/v1beta1/coin.js'; @@ -39,19 +39,6 @@ harden(ChainAddressShape); export const Proto3Shape = { typeUrl: M.string(), value: M.string() }; harden(ChainAddressShape); -/** @internal */ -export const IBCTransferOptionsShape = M.splitRecord( - {}, - { - timeoutTimestamp: M.bigint(), - timeoutHeight: { - revisionHeight: M.bigint(), - revisionNumber: M.bigint(), - }, - memo: M.string(), - }, -); - /** @internal */ export const IBCChannelIDShape = M.string(); @@ -247,3 +234,34 @@ export const ForwardInfoShape = { }), }; harden(ForwardInfoShape); + +/** + * Caller configurable values of {@link ForwardInfo} + * + * @type {TypedPattern} + */ +export const ForwardOptsShape = M.splitRecord( + {}, + { + timeout: M.string(), + retries: M.number(), + }, + {}, +); + +/** + * @type {TypedPattern} + * @internal + */ +export const IBCTransferOptionsShape = M.splitRecord( + {}, + { + timeoutTimestamp: M.bigint(), + timeoutHeight: { + revisionHeight: M.bigint(), + revisionNumber: M.bigint(), + }, + memo: M.string(), + forwardOpts: ForwardOptsShape, + }, +); diff --git a/packages/orchestration/src/utils/asset.js b/packages/orchestration/src/utils/asset.js index 3a39da7135b..2f4efccd998 100644 --- a/packages/orchestration/src/utils/asset.js +++ b/packages/orchestration/src/utils/asset.js @@ -8,19 +8,18 @@ import { denomHash } from './denomHash.js'; * Helper function for creating {@link DenomDetail} data for {@link ChainHub} * asset registration. * - * TODO #10580 remove 'brandKey' in favor of `LegibleCapData` - * * @param {Denom} baseDenom * @param {string} baseName + * @param {Brand<'nat'>} [brand] * @param {string} [chainName] * @param {Record} [infoOf] - * @param {string} [brandKey] - * @returns {[string, DenomDetail & { brandKey?: string }]} + * @returns {[string, DenomDetail]} */ -export const assetOn = (baseDenom, baseName, chainName, infoOf, brandKey) => { - if (!chainName) { - return [baseDenom, { baseName, chainName: baseName, baseDenom }]; - } +export const assetOn = (baseDenom, baseName, brand, chainName, infoOf) => { + const baseDetail = { baseName, chainName: chainName || baseName, baseDenom }; + const detail = brand ? { ...baseDetail, brand } : baseDetail; + if (!chainName) return harden([baseDenom, detail]); + if (!infoOf) throw Error(`must provide infoOf`); const issuerInfo = infoOf[baseName]; const holdingInfo = infoOf[chainName]; @@ -30,5 +29,5 @@ export const assetOn = (baseDenom, baseName, chainName, infoOf, brandKey) => { const { channelId } = holdingInfo.connections[issuerInfo.chainId].transferChannel; const denom = `ibc/${denomHash({ denom: baseDenom, channelId })}`; - return [denom, { baseName, chainName, baseDenom, brandKey }]; + return harden([denom, detail]); }; diff --git a/packages/orchestration/test/examples/snapshots/send-anywhere.test.ts.md b/packages/orchestration/test/examples/snapshots/send-anywhere.test.ts.md index cb7e77f4a24..df818b1eba8 100644 --- a/packages/orchestration/test/examples/snapshots/send-anywhere.test.ts.md +++ b/packages/orchestration/test/examples/snapshots/send-anywhere.test.ts.md @@ -36,22 +36,1117 @@ Generated by [AVA](https://avajs.dev). ChainHub_singleton: 'Alleged: ChainHub', bech32PrefixToChainName: { agoric: 'agoric', + celestia: 'celestia', + cosmos: 'cosmoshub', + dydx: 'dydx', + juno: 'juno', + neutron: 'neutron', + noble: 'noble', + omniflix: 'omniflixhub', + osmo: 'osmosis', + secret: 'secretnetwork', + stars: 'stargaze', + stride: 'stride', + umee: 'umee', + }, + brandDenom: { + 'Alleged: BLD brand': 'ubld', + 'Alleged: IST brand': 'uist', }, - brandDenom: {}, chainInfos: { agoric: { bech32Prefix: 'agoric', chainId: 'agoric-3', icqEnabled: false, + pfmEnabled: true, stakingTokens: [ { denom: 'ubld', }, ], }, + celestia: { + bech32Prefix: 'celestia', + chainId: 'celestia', + icqEnabled: false, + pfmEnabled: true, + stakingTokens: [ + { + denom: 'utia', + }, + ], + }, + cosmoshub: { + bech32Prefix: 'cosmos', + chainId: 'cosmoshub-4', + icqEnabled: false, + pfmEnabled: true, + stakingTokens: [ + { + denom: 'uatom', + }, + ], + }, + dydx: { + bech32Prefix: 'dydx', + chainId: 'dydx-mainnet-1', + icqEnabled: false, + pfmEnabled: false, + stakingTokens: [ + { + denom: 'adydx', + }, + ], + }, + juno: { + bech32Prefix: 'juno', + chainId: 'juno-1', + icqEnabled: false, + pfmEnabled: true, + stakingTokens: [ + { + denom: 'ujuno', + }, + ], + }, + neutron: { + bech32Prefix: 'neutron', + chainId: 'neutron-1', + icqEnabled: false, + pfmEnabled: true, + stakingTokens: [ + { + denom: 'untrn', + }, + ], + }, + noble: { + bech32Prefix: 'noble', + chainId: 'noble-1', + icqEnabled: false, + pfmEnabled: true, + }, + omniflixhub: { + bech32Prefix: 'omniflix', + chainId: 'omniflixhub-1', + icqEnabled: true, + pfmEnabled: true, + stakingTokens: [ + { + denom: 'uflix', + }, + ], + }, + osmosis: { + bech32Prefix: 'osmo', + chainId: 'osmosis-1', + icqEnabled: true, + pfmEnabled: true, + stakingTokens: [ + { + denom: 'uosmo', + }, + ], + }, + secretnetwork: { + bech32Prefix: 'secret', + chainId: 'secret-4', + icqEnabled: false, + pfmEnabled: true, + stakingTokens: [ + { + denom: 'uscrt', + }, + ], + }, + stargaze: { + bech32Prefix: 'stars', + chainId: 'stargaze-1', + icqEnabled: false, + pfmEnabled: true, + stakingTokens: [ + { + denom: 'ustars', + }, + ], + }, + stride: { + bech32Prefix: 'stride', + chainId: 'stride-1', + icqEnabled: false, + pfmEnabled: true, + stakingTokens: [ + { + denom: 'ustrd', + }, + ], + }, + umee: { + bech32Prefix: 'umee', + chainId: 'umee-1', + icqEnabled: false, + pfmEnabled: true, + stakingTokens: [ + { + denom: 'uumee', + }, + ], + }, + }, + connectionInfos: { + 'agoric-3_cosmoshub-4': { + client_id: '07-tendermint-6', + counterparty: { + client_id: '07-tendermint-927', + connection_id: 'connection-649', + }, + id: 'connection-8', + state: 3, + transferChannel: { + channelId: 'channel-5', + counterPartyChannelId: 'channel-405', + counterPartyPortId: 'transfer', + ordering: 0, + portId: 'transfer', + state: 3, + version: 'ics20-1', + }, + }, + 'agoric-3_noble-1': { + client_id: '07-tendermint-77', + counterparty: { + client_id: '07-tendermint-32', + connection_id: 'connection-40', + }, + id: 'connection-72', + state: 3, + transferChannel: { + channelId: 'channel-62', + counterPartyChannelId: 'channel-21', + counterPartyPortId: 'transfer', + ordering: 0, + portId: 'transfer', + state: 3, + version: 'ics20-1', + }, + }, + 'agoric-3_omniflixhub-1': { + client_id: '07-tendermint-73', + counterparty: { + client_id: '07-tendermint-47', + connection_id: 'connection-40', + }, + id: 'connection-67', + state: 3, + transferChannel: { + channelId: 'channel-58', + counterPartyChannelId: 'channel-30', + counterPartyPortId: 'transfer', + ordering: 0, + portId: 'transfer', + state: 3, + version: 'ics20-1', + }, + }, + 'agoric-3_osmosis-1': { + client_id: '07-tendermint-1', + counterparty: { + client_id: '07-tendermint-2109', + connection_id: 'connection-1649', + }, + id: 'connection-1', + state: 3, + transferChannel: { + channelId: 'channel-1', + counterPartyChannelId: 'channel-320', + counterPartyPortId: 'transfer', + ordering: 0, + portId: 'transfer', + state: 3, + version: 'ics20-1', + }, + }, + 'agoric-3_secret-4': { + client_id: '07-tendermint-17', + counterparty: { + client_id: '07-tendermint-111', + connection_id: 'connection-80', + }, + id: 'connection-17', + state: 3, + transferChannel: { + channelId: 'channel-10', + counterPartyChannelId: 'channel-51', + counterPartyPortId: 'transfer', + ordering: 0, + portId: 'transfer', + state: 3, + version: 'ics20-1', + }, + }, + 'agoric-3_stride-1': { + client_id: '07-tendermint-74', + counterparty: { + client_id: '07-tendermint-129', + connection_id: 'connection-118', + }, + id: 'connection-68', + state: 3, + transferChannel: { + channelId: 'channel-59', + counterPartyChannelId: 'channel-148', + counterPartyPortId: 'transfer', + ordering: 0, + portId: 'transfer', + state: 3, + version: 'ics20-1', + }, + }, + 'agoric-3_umee-1': { + client_id: '07-tendermint-18', + counterparty: { + client_id: '07-tendermint-152', + connection_id: 'connection-101', + }, + id: 'connection-18', + state: 3, + transferChannel: { + channelId: 'channel-11', + counterPartyChannelId: 'channel-42', + counterPartyPortId: 'transfer', + ordering: 0, + portId: 'transfer', + state: 3, + version: 'ics20-1', + }, + }, + 'celestia_neutron-1': { + client_id: '07-tendermint-29', + counterparty: { + client_id: '07-tendermint-48', + connection_id: 'connection-36', + }, + id: 'connection-7', + state: 3, + transferChannel: { + channelId: 'channel-8', + counterPartyChannelId: 'channel-35', + counterPartyPortId: 'transfer', + ordering: 0, + portId: 'transfer', + state: 3, + version: 'ics20-1', + }, + }, + 'celestia_osmosis-1': { + client_id: '07-tendermint-10', + counterparty: { + client_id: '07-tendermint-3012', + connection_id: 'connection-2503', + }, + id: 'connection-2', + state: 3, + transferChannel: { + channelId: 'channel-2', + counterPartyChannelId: 'channel-6994', + counterPartyPortId: 'transfer', + ordering: 0, + portId: 'transfer', + state: 3, + version: 'ics20-1', + }, + }, + 'celestia_secret-4': { + client_id: '07-tendermint-52', + counterparty: { + client_id: '07-tendermint-174', + connection_id: 'connection-131', + }, + id: 'connection-15', + state: 3, + transferChannel: { + channelId: 'channel-14', + counterPartyChannelId: 'channel-91', + counterPartyPortId: 'transfer', + ordering: 0, + portId: 'transfer', + state: 3, + version: 'ics20-1', + }, + }, + 'celestia_stargaze-1': { + client_id: '07-tendermint-86', + counterparty: { + client_id: '07-tendermint-359', + connection_id: 'connection-296', + }, + id: 'connection-56', + state: 3, + transferChannel: { + channelId: 'channel-33', + counterPartyChannelId: 'channel-291', + counterPartyPortId: 'transfer', + ordering: 0, + portId: 'transfer', + state: 3, + version: 'ics20-1', + }, + }, + 'celestia_stride-1': { + client_id: '07-tendermint-0', + counterparty: { + client_id: '07-tendermint-137', + connection_id: 'connection-125', + }, + id: 'connection-4', + state: 3, + transferChannel: { + channelId: 'channel-4', + counterPartyChannelId: 'channel-162', + counterPartyPortId: 'transfer', + ordering: 0, + portId: 'transfer', + state: 3, + version: 'ics20-1', + }, + }, + 'cosmoshub-4_juno-1': { + client_id: '07-tendermint-439', + counterparty: { + client_id: '07-tendermint-3', + connection_id: 'connection-2', + }, + id: 'connection-372', + state: 3, + transferChannel: { + channelId: 'channel-207', + counterPartyChannelId: 'channel-1', + counterPartyPortId: 'transfer', + ordering: 0, + portId: 'transfer', + state: 3, + version: 'ics20-1', + }, + }, + 'cosmoshub-4_neutron-1': { + client_id: '07-tendermint-1119', + counterparty: { + client_id: '07-tendermint-0', + connection_id: 'connection-0', + }, + id: 'connection-809', + state: 3, + transferChannel: { + channelId: 'channel-569', + counterPartyChannelId: 'channel-1', + counterPartyPortId: 'transfer', + ordering: 0, + portId: 'transfer', + state: 3, + version: 'ics20-1', + }, + }, + 'cosmoshub-4_noble-1': { + client_id: '07-tendermint-1116', + counterparty: { + client_id: '07-tendermint-4', + connection_id: 'connection-12', + }, + id: 'connection-790', + state: 3, + transferChannel: { + channelId: 'channel-536', + counterPartyChannelId: 'channel-4', + counterPartyPortId: 'transfer', + ordering: 0, + portId: 'transfer', + state: 3, + version: 'ics20-1', + }, + }, + 'cosmoshub-4_omniflixhub-1': { + client_id: '07-tendermint-656', + counterparty: { + client_id: '07-tendermint-23', + connection_id: 'connection-19', + }, + id: 'connection-501', + state: 3, + transferChannel: { + channelId: 'channel-306', + counterPartyChannelId: 'channel-12', + counterPartyPortId: 'transfer', + ordering: 0, + portId: 'transfer', + state: 3, + version: 'ics20-1', + }, + }, + 'cosmoshub-4_osmosis-1': { + client_id: '07-tendermint-259', + counterparty: { + client_id: '07-tendermint-1', + connection_id: 'connection-1', + }, + id: 'connection-257', + state: 3, + transferChannel: { + channelId: 'channel-141', + counterPartyChannelId: 'channel-0', + counterPartyPortId: 'transfer', + ordering: 0, + portId: 'transfer', + state: 3, + version: 'ics20-1', + }, + }, + 'cosmoshub-4_secret-4': { + client_id: '07-tendermint-492', + counterparty: { + client_id: '07-tendermint-1', + connection_id: 'connection-0', + }, + id: 'connection-401', + state: 3, + transferChannel: { + channelId: 'channel-235', + counterPartyChannelId: 'channel-0', + counterPartyPortId: 'transfer', + ordering: 0, + portId: 'transfer', + state: 3, + version: 'ics20-1', + }, + }, + 'cosmoshub-4_stargaze-1': { + client_id: '07-tendermint-1188', + counterparty: { + client_id: '07-tendermint-320', + connection_id: 'connection-256', + }, + id: 'connection-918', + state: 3, + transferChannel: { + channelId: 'channel-730', + counterPartyChannelId: 'channel-239', + counterPartyPortId: 'transfer', + ordering: 0, + portId: 'transfer', + state: 3, + version: 'ics20-1', + }, + }, + 'cosmoshub-4_stride-1': { + client_id: '07-tendermint-913', + counterparty: { + client_id: '07-tendermint-0', + connection_id: 'connection-0', + }, + id: 'connection-635', + state: 3, + transferChannel: { + channelId: 'channel-391', + counterPartyChannelId: 'channel-0', + counterPartyPortId: 'transfer', + ordering: 0, + portId: 'transfer', + state: 3, + version: 'ics20-1', + }, + }, + 'dydx-mainnet-1_neutron-1': { + client_id: '07-tendermint-11', + counterparty: { + client_id: '07-tendermint-72', + connection_id: 'connection-51', + }, + id: 'connection-17', + state: 3, + transferChannel: { + channelId: 'channel-11', + counterPartyChannelId: 'channel-48', + counterPartyPortId: 'transfer', + ordering: 0, + portId: 'transfer', + state: 3, + version: 'ics20-1', + }, + }, + 'dydx-mainnet-1_noble-1': { + client_id: '07-tendermint-0', + counterparty: { + client_id: '07-tendermint-59', + connection_id: 'connection-57', + }, + id: 'connection-0', + state: 3, + transferChannel: { + channelId: 'channel-0', + counterPartyChannelId: 'channel-33', + counterPartyPortId: 'transfer', + ordering: 0, + portId: 'transfer', + state: 3, + version: 'ics20-1', + }, + }, + 'dydx-mainnet-1_osmosis-1': { + client_id: '07-tendermint-3', + counterparty: { + client_id: '07-tendermint-3009', + connection_id: 'connection-2500', + }, + id: 'connection-7', + state: 3, + transferChannel: { + channelId: 'channel-3', + counterPartyChannelId: 'channel-6787', + counterPartyPortId: 'transfer', + ordering: 0, + portId: 'transfer', + state: 3, + version: 'ics20-1', + }, + }, + 'dydx-mainnet-1_stride-1': { + client_id: '07-tendermint-1', + counterparty: { + client_id: '07-tendermint-133', + connection_id: 'connection-123', + }, + id: 'connection-1', + state: 3, + transferChannel: { + channelId: 'channel-1', + counterPartyChannelId: 'channel-160', + counterPartyPortId: 'transfer', + ordering: 0, + portId: 'transfer', + state: 3, + version: 'ics20-1', + }, + }, + 'dydx-mainnet-1_umee-1': { + client_id: '07-tendermint-8', + counterparty: { + client_id: '07-tendermint-244', + connection_id: 'connection-208', + }, + id: 'connection-13', + state: 3, + transferChannel: { + channelId: 'channel-8', + counterPartyChannelId: 'channel-118', + counterPartyPortId: 'transfer', + ordering: 0, + portId: 'transfer', + state: 3, + version: 'ics20-1', + }, + }, + 'juno-1_neutron-1': { + client_id: '07-tendermint-557', + counterparty: { + client_id: '07-tendermint-97', + connection_id: 'connection-71', + }, + id: 'connection-524', + state: 3, + transferChannel: { + channelId: 'channel-548', + counterPartyChannelId: 'channel-4328', + counterPartyPortId: 'transfer', + ordering: 0, + portId: 'transfer', + state: 3, + version: 'ics20-1', + }, + }, + 'juno-1_noble-1': { + client_id: '07-tendermint-334', + counterparty: { + client_id: '07-tendermint-3', + connection_id: 'connection-8', + }, + id: 'connection-322', + state: 3, + transferChannel: { + channelId: 'channel-224', + counterPartyChannelId: 'channel-3', + counterPartyPortId: 'transfer', + ordering: 0, + portId: 'transfer', + state: 3, + version: 'ics20-1', + }, + }, + 'juno-1_osmosis-1': { + client_id: '07-tendermint-0', + counterparty: { + client_id: '07-tendermint-1457', + connection_id: 'connection-1142', + }, + id: 'connection-0', + state: 3, + transferChannel: { + channelId: 'channel-0', + counterPartyChannelId: 'channel-42', + counterPartyPortId: 'transfer', + ordering: 0, + portId: 'transfer', + state: 3, + version: 'ics20-1', + }, + }, + 'juno-1_secret-4': { + client_id: '07-tendermint-108', + counterparty: { + client_id: '07-tendermint-23', + connection_id: 'connection-9', + }, + id: 'connection-68', + state: 3, + transferChannel: { + channelId: 'channel-48', + counterPartyChannelId: 'channel-8', + counterPartyPortId: 'transfer', + ordering: 0, + portId: 'transfer', + state: 3, + version: 'ics20-1', + }, + }, + 'juno-1_stargaze-1': { + client_id: '07-tendermint-44', + counterparty: { + client_id: '07-tendermint-13', + connection_id: 'connection-11', + }, + id: 'connection-30', + state: 3, + transferChannel: { + channelId: 'channel-20', + counterPartyChannelId: 'channel-5', + counterPartyPortId: 'transfer', + ordering: 0, + portId: 'transfer', + state: 3, + version: 'ics20-1', + }, + }, + 'juno-1_stride-1': { + client_id: '07-tendermint-263', + counterparty: { + client_id: '07-tendermint-31', + connection_id: 'connection-19', + }, + id: 'connection-205', + state: 3, + transferChannel: { + channelId: 'channel-139', + counterPartyChannelId: 'channel-24', + counterPartyPortId: 'transfer', + ordering: 0, + portId: 'transfer', + state: 3, + version: 'ics20-1', + }, + }, + 'neutron-1_noble-1': { + client_id: '07-tendermint-40', + counterparty: { + client_id: '07-tendermint-25', + connection_id: 'connection-34', + }, + id: 'connection-31', + state: 3, + transferChannel: { + channelId: 'channel-30', + counterPartyChannelId: 'channel-18', + counterPartyPortId: 'transfer', + ordering: 0, + portId: 'transfer', + state: 3, + version: 'ics20-1', + }, + }, + 'neutron-1_osmosis-1': { + client_id: '07-tendermint-19', + counterparty: { + client_id: '07-tendermint-2823', + connection_id: 'connection-2338', + }, + id: 'connection-18', + state: 3, + transferChannel: { + channelId: 'channel-10', + counterPartyChannelId: 'channel-874', + counterPartyPortId: 'transfer', + ordering: 0, + portId: 'transfer', + state: 3, + version: 'ics20-1', + }, + }, + 'neutron-1_secret-4': { + client_id: '07-tendermint-85', + counterparty: { + client_id: '07-tendermint-199', + connection_id: 'connection-192', + }, + id: 'connection-63', + state: 3, + transferChannel: { + channelId: 'channel-1551', + counterPartyChannelId: 'channel-144', + counterPartyPortId: 'transfer', + ordering: 0, + portId: 'transfer', + state: 3, + version: 'ics20-1', + }, + }, + 'neutron-1_stargaze-1': { + client_id: '07-tendermint-31', + counterparty: { + client_id: '07-tendermint-283', + connection_id: 'connection-211', + }, + id: 'connection-23', + state: 3, + transferChannel: { + channelId: 'channel-18', + counterPartyChannelId: 'channel-191', + counterPartyPortId: 'transfer', + ordering: 0, + portId: 'transfer', + state: 3, + version: 'ics20-1', + }, + }, + 'neutron-1_stride-1': { + client_id: '07-tendermint-18', + counterparty: { + client_id: '07-tendermint-125', + connection_id: 'connection-113', + }, + id: 'connection-15', + state: 3, + transferChannel: { + channelId: 'channel-8', + counterPartyChannelId: 'channel-123', + counterPartyPortId: 'transfer', + ordering: 0, + portId: 'transfer', + state: 3, + version: 'ics20-1', + }, + }, + 'noble-1_omniflixhub-1': { + client_id: '07-tendermint-68', + counterparty: { + client_id: '07-tendermint-51', + connection_id: 'connection-49', + }, + id: 'connection-65', + state: 3, + transferChannel: { + channelId: 'channel-44', + counterPartyChannelId: 'channel-38', + counterPartyPortId: 'transfer', + ordering: 0, + portId: 'transfer', + state: 3, + version: 'ics20-1', + }, + }, + 'noble-1_osmosis-1': { + client_id: '07-tendermint-0', + counterparty: { + client_id: '07-tendermint-2704', + connection_id: 'connection-2241', + }, + id: 'connection-2', + state: 3, + transferChannel: { + channelId: 'channel-1', + counterPartyChannelId: 'channel-750', + counterPartyPortId: 'transfer', + ordering: 0, + portId: 'transfer', + state: 3, + version: 'ics20-1', + }, + }, + 'noble-1_secret-4': { + client_id: '07-tendermint-24', + counterparty: { + client_id: '07-tendermint-170', + connection_id: 'connection-127', + }, + id: 'connection-33', + state: 3, + transferChannel: { + channelId: 'channel-17', + counterPartyChannelId: 'channel-88', + counterPartyPortId: 'transfer', + ordering: 0, + portId: 'transfer', + state: 3, + version: 'ics20-1', + }, + }, + 'noble-1_stargaze-1': { + client_id: '07-tendermint-16', + counterparty: { + client_id: '07-tendermint-287', + connection_id: 'connection-214', + }, + id: 'connection-25', + state: 3, + transferChannel: { + channelId: 'channel-11', + counterPartyChannelId: 'channel-204', + counterPartyPortId: 'transfer', + ordering: 0, + portId: 'transfer', + state: 3, + version: 'ics20-1', + }, + }, + 'noble-1_umee-1': { + client_id: '07-tendermint-73', + counterparty: { + client_id: '07-tendermint-248', + connection_id: 'connection-210', + }, + id: 'connection-74', + state: 3, + transferChannel: { + channelId: 'channel-51', + counterPartyChannelId: 'channel-120', + counterPartyPortId: 'transfer', + ordering: 0, + portId: 'transfer', + state: 3, + version: 'ics20-1', + }, + }, + 'omniflixhub-1_osmosis-1': { + client_id: '07-tendermint-8', + counterparty: { + client_id: '07-tendermint-1829', + connection_id: 'connection-1431', + }, + id: 'connection-8', + state: 3, + transferChannel: { + channelId: 'channel-1', + counterPartyChannelId: 'channel-199', + counterPartyPortId: 'transfer', + ordering: 0, + portId: 'transfer', + state: 3, + version: 'ics20-1', + }, + }, + 'osmosis-1_secret-4': { + client_id: '07-tendermint-1588', + counterparty: { + client_id: '07-tendermint-2', + connection_id: 'connection-1', + }, + id: 'connection-1244', + state: 3, + transferChannel: { + channelId: 'channel-88', + counterPartyChannelId: 'channel-1', + counterPartyPortId: 'transfer', + ordering: 0, + portId: 'transfer', + state: 3, + version: 'ics20-1', + }, + }, + 'osmosis-1_stargaze-1': { + client_id: '07-tendermint-1562', + counterparty: { + client_id: '07-tendermint-0', + connection_id: 'connection-0', + }, + id: 'connection-1223', + state: 3, + transferChannel: { + channelId: 'channel-75', + counterPartyChannelId: 'channel-0', + counterPartyPortId: 'transfer', + ordering: 0, + portId: 'transfer', + state: 3, + version: 'ics20-1', + }, + }, + 'osmosis-1_stride-1': { + client_id: '07-tendermint-2119', + counterparty: { + client_id: '07-tendermint-1', + connection_id: 'connection-2', + }, + id: 'connection-1657', + state: 3, + transferChannel: { + channelId: 'channel-326', + counterPartyChannelId: 'channel-5', + counterPartyPortId: 'transfer', + ordering: 0, + portId: 'transfer', + state: 3, + version: 'ics20-1', + }, + }, + 'osmosis-1_umee-1': { + client_id: '07-tendermint-1805', + counterparty: { + client_id: '07-tendermint-6', + connection_id: 'connection-0', + }, + id: 'connection-1410', + state: 3, + transferChannel: { + channelId: 'channel-184', + counterPartyChannelId: 'channel-0', + counterPartyPortId: 'transfer', + ordering: 0, + portId: 'transfer', + state: 3, + version: 'ics20-1', + }, + }, + 'secret-4_stargaze-1': { + client_id: '07-tendermint-43', + counterparty: { + client_id: '07-tendermint-177', + connection_id: 'connection-110', + }, + id: 'connection-25', + state: 3, + transferChannel: { + channelId: 'channel-19', + counterPartyChannelId: 'channel-48', + counterPartyPortId: 'transfer', + ordering: 0, + portId: 'transfer', + state: 3, + version: 'ics20-1', + }, + }, + 'secret-4_stride-1': { + client_id: '07-tendermint-75', + counterparty: { + client_id: '07-tendermint-37', + connection_id: 'connection-25', + }, + id: 'connection-40', + state: 3, + transferChannel: { + channelId: 'channel-37', + counterPartyChannelId: 'channel-40', + counterPartyPortId: 'transfer', + ordering: 0, + portId: 'transfer', + state: 3, + version: 'ics20-1', + }, + }, + 'secret-4_umee-1': { + client_id: '07-tendermint-193', + counterparty: { + client_id: '07-tendermint-249', + connection_id: 'connection-213', + }, + id: 'connection-188', + state: 3, + transferChannel: { + channelId: 'channel-126', + counterPartyChannelId: 'channel-123', + counterPartyPortId: 'transfer', + ordering: 0, + portId: 'transfer', + state: 3, + version: 'ics20-1', + }, + }, + 'stargaze-1_stride-1': { + client_id: '07-tendermint-195', + counterparty: { + client_id: '07-tendermint-30', + connection_id: 'connection-18', + }, + id: 'connection-128', + state: 3, + transferChannel: { + channelId: 'channel-106', + counterPartyChannelId: 'channel-19', + counterPartyPortId: 'transfer', + ordering: 0, + portId: 'transfer', + state: 3, + version: 'ics20-1', + }, + }, + 'stride-1_umee-1': { + client_id: '07-tendermint-32', + counterparty: { + client_id: '07-tendermint-64', + connection_id: 'connection-45', + }, + id: 'connection-20', + state: 3, + transferChannel: { + channelId: 'channel-29', + counterPartyChannelId: 'channel-34', + counterPartyPortId: 'transfer', + ordering: 0, + portId: 'transfer', + state: 3, + version: 'ics20-1', + }, + }, + }, + denom: { + 'agoric:ibc/BA313C4A19DFBF943586C0387E6B11286F9E416B4DD27574E6909CABE0E342FA': { + baseDenom: 'uatom', + baseName: 'cosmoshub', + chainName: 'agoric', + }, + 'agoric:ibc/FE98AAD68F02F03565E9FA39A5E627946699B2B07115889ED812D8BA639576A9': { + baseDenom: 'uusdc', + baseName: 'noble', + chainName: 'agoric', + }, + 'agoric:ubld': { + baseDenom: 'ubld', + baseName: 'agoric', + brand: Object @Alleged: BLD brand {}, + chainName: 'agoric', + }, + 'agoric:uist': { + baseDenom: 'uist', + baseName: 'agoric', + brand: Object @Alleged: IST brand {}, + chainName: 'agoric', + }, + 'cosmoshub:ibc/9EA9BCC30570DC3198317BB6B5561AB41DDC17AFC342087022C128C57EFE19BA': { + baseDenom: 'uist', + baseName: 'agoric', + chainName: 'cosmoshub', + }, + 'dydx:ibc/8E27BA2D5493AF5636760E354E46004562C46AB7EC0CC4C1CA14E9E20E2545B5': { + baseDenom: 'uusdc', + baseName: 'noble', + chainName: 'dydx', + }, }, - connectionInfos: {}, - denom: {}, lookupChainInfo_kindHandle: 'Alleged: kind', lookupChainsAndConnection_kindHandle: 'Alleged: kind', lookupConnectionInfo_kindHandle: 'Alleged: kind', diff --git a/packages/orchestration/test/examples/snapshots/send-anywhere.test.ts.snap b/packages/orchestration/test/examples/snapshots/send-anywhere.test.ts.snap index 019aa9a9d1a..3cf1a7c6eca 100644 Binary files a/packages/orchestration/test/examples/snapshots/send-anywhere.test.ts.snap and b/packages/orchestration/test/examples/snapshots/send-anywhere.test.ts.snap differ diff --git a/packages/orchestration/test/exos/chain-hub.test.ts b/packages/orchestration/test/exos/chain-hub.test.ts index 1a8efc8e762..02505f41778 100644 --- a/packages/orchestration/test/exos/chain-hub.test.ts +++ b/packages/orchestration/test/exos/chain-hub.test.ts @@ -201,12 +201,14 @@ test('makeChainAddress', async t => { const [uusdcOnAgoric, agDetail] = assetOn( 'uusdc', 'noble', + undefined, 'agoric', knownChains, ); const [uusdcOnOsmosis, osDetail] = assetOn( 'uusdc', 'noble', + undefined, 'osmosis', knownChains, ); @@ -329,7 +331,7 @@ test('makeTransferRoute - through issuing chain', async t => { }); // use TransferRoute to build a MsgTransfer - if (!route || !('forwardInfo' in route)) { + if (!('forwardInfo' in route)) { throw new Error('forwardInfo not returned'); // appease tsc... } @@ -383,6 +385,18 @@ test('makeTransferRoute - takes forwardOpts', t => { }, }); + t.like( + chainHub.makeTransferRoute(dest, amt, 'osmosis', { timeout: '99min' }), + { + forwardInfo: { + forward: { + timeout: '99min', + }, + }, + }, + 'each field is optional', + ); + // test that typeGuard works t.throws( () => @@ -395,7 +409,7 @@ test('makeTransferRoute - takes forwardOpts', t => { forward: JSON.stringify('stringified nested forward data'), }), ), - { message: /Must not have unexpected properties/ }, + { message: /In "makeTransferRoute" method of/ }, ); }); @@ -476,7 +490,7 @@ test('makeTransferRoute - no connection info single hop', t => { harden({ denom: uusdcOnAgoric, value: 100n }), 'agoric', ), - { message: 'no connection info found for "agoric-3_noble-1"' }, + { message: 'no connection info found for "agoric-3"<->"noble-1"' }, ); }); @@ -505,7 +519,7 @@ test('makeTransferRoute - no connection info multi hop', t => { harden({ denom: uusdcOnAgoric, value: 100n }), 'agoric', ), - { message: 'no connection info found for "noble-1_osmosis-1"' }, + { message: 'no connection info found for "noble-1"<->"osmosis-1"' }, ); // transfer USDC on osmosis to agoric @@ -516,7 +530,7 @@ test('makeTransferRoute - no connection info multi hop', t => { harden({ denom: uusdcOnOsmosis, value: 100n }), 'osmosis', ), - { message: 'no connection info found for "noble-1_osmosis-1"' }, + { message: 'no connection info found for "osmosis-1"<->"noble-1"' }, ); }); diff --git a/packages/orchestration/test/exos/cosmos-orchestration-account.test.ts b/packages/orchestration/test/exos/cosmos-orchestration-account.test.ts index d3454066473..1cfb26d7a2f 100644 --- a/packages/orchestration/test/exos/cosmos-orchestration-account.test.ts +++ b/packages/orchestration/test/exos/cosmos-orchestration-account.test.ts @@ -14,6 +14,10 @@ import { QueryBalanceRequest, QueryBalanceResponse, } from '@agoric/cosmic-proto/cosmos/bank/v1beta1/query.js'; +import { + MsgSend, + MsgSendResponse, +} from '@agoric/cosmic-proto/cosmos/bank/v1beta1/tx.js'; import { Coin } from '@agoric/cosmic-proto/cosmos/base/v1beta1/coin.js'; import { QueryDelegationRequest, @@ -38,6 +42,8 @@ import { MsgDelegate, MsgDelegateResponse, } from '@agoric/cosmic-proto/cosmos/staking/v1beta1/tx.js'; +import { withAmountUtils } from '@agoric/zoe/tools/test-utils.js'; +import { makeIssuerKit } from '@agoric/ertp'; import { decodeBase64 } from '@endo/base64'; import { commonSetup } from '../supports.js'; import type { @@ -55,6 +61,8 @@ import { } from '../../tools/ibc-mocks.js'; import type { CosmosValidatorAddress } from '../../src/cosmos-api.js'; import { protoMsgMocks } from '../ibc-mocks.js'; +import fetchedChainInfo from '../../src/fetched-chain-info.js'; +import { assetOn } from '../../src/utils/asset.js'; type TestContext = Awaited>; @@ -64,11 +72,21 @@ test.beforeEach(async t => { t.context = await commonSetup(t); }); +const [uistOnCosmos] = assetOn( + 'uist', + 'agoric', + undefined, + 'cosmoshub', + fetchedChainInfo, +); + test('send (to addr on same chain)', async t => { const { brands: { ist }, - utils: { inspectDibcBridge }, + mocks: { ibcBridge }, + utils: { inspectDibcBridge, populateChainHub }, } = t.context; + populateChainHub(); const makeTestCOAKit = prepareMakeTestCOAKit(t, t.context); const account = await makeTestCOAKit(); t.assert(account, 'account is returned'); @@ -88,6 +106,42 @@ test('send (to addr on same chain)', async t => { undefined, ); + // register handler for ist bank send + ibcBridge.addMockAck( + buildTxPacketString([ + MsgSend.toProtoMsg({ + fromAddress: 'cosmos1test', + toAddress: 'cosmos99test', + amount: [ + { + denom: uistOnCosmos, + // denom: 'ibc/uisthash', + amount: '10', + }, + ], + }), + ]), + buildMsgResponseString(MsgSendResponse, {}), + ); + + t.is( + await E(account).send(toAddress, { + denom: uistOnCosmos, + value: 10n, + } as AmountArg), + undefined, + 'send accepts Amount', + ); + + await t.throwsAsync( + () => E(account).send(toAddress, ist.make(10n) as AmountArg), + { + message: + "'amountToCoin' not working for \"[Alleged: IST brand]\" until #10449; use 'DenomAmount' for now", + }, + 'TODO #10449 amountToCoin for CosmosOrchestrationAccount', + ); + // simulate timeout error await t.throwsAsync( E(account).send(toAddress, { value: 504n, denom: 'uatom' } as AmountArg), @@ -95,10 +149,17 @@ test('send (to addr on same chain)', async t => { { message: 'ABCI code: 5: error handling packet: see events for details' }, ); - // IST not registered - await t.throwsAsync(E(account).send(toAddress, ist.make(10n) as AmountArg), { - message: 'No denom for brand [object Alleged: IST brand]', - }); + // MOO brand not registered + const moolah = withAmountUtils(makeIssuerKit('MOO')); + await t.throwsAsync( + E(account).send(toAddress, moolah.make(10n) as AmountArg), + { + // TODO #10449 + // message: 'No denom for brand [object Alleged: MOO brand]', + message: + "'amountToCoin' not working for \"[Alleged: MOO brand]\" until #10449; use 'DenomAmount' for now", + }, + ); // multi-send (sendAll) t.is( @@ -110,11 +171,10 @@ test('send (to addr on same chain)', async t => { ); const { bridgeDowncalls } = await inspectDibcBridge(); - t.is( bridgeDowncalls.filter(d => d.method === 'sendPacket').length, - 3, - 'sent 2 successful txs and 1 failed. 1 rejected before sending', + 4, + 'sent 3 successful txs and 1 failed. 1 rejected before sending', ); }); @@ -122,9 +182,10 @@ test('transfer', async t => { const { brands: { ist }, facadeServices: { chainHub }, - utils: { inspectDibcBridge }, + utils: { inspectDibcBridge, populateChainHub }, mocks: { ibcBridge }, } = t.context; + populateChainHub(); const mockIbcTransfer = { sourcePort: 'transfer', @@ -184,6 +245,14 @@ test('transfer', async t => { }, }); + const umooTransfer = toTransferTxPacket({ + ...mockIbcTransfer, + token: { + denom: 'umoo', + amount: '10', + }, + }); + return { [defaultTransfer]: transferResp, [customTimeoutHeight]: transferResp, @@ -191,6 +260,7 @@ test('transfer', async t => { [customTimeout]: transferResp, [customMemo]: transferResp, [uistTransfer]: transferResp, + [umooTransfer]: transferResp, }; }; ibcBridge.setMockAck(buildMocks()); @@ -307,18 +377,26 @@ test('transfer', async t => { }, ); + const moolah = withAmountUtils(makeIssuerKit('MOO')); t.log('transfer throws if asset is not in its chainHub'); - await t.throwsAsync(E(account).transfer(mockDestination, ist.make(10n)), { - message: 'No denom for brand [object Alleged: IST brand]', + await t.throwsAsync(E(account).transfer(mockDestination, moolah.make(10n)), { + // TODO #10449 + // message: 'No denom for brand [object Alleged: MOO brand]', + message: + "'amountToCoin' not working for \"[Alleged: MOO brand]\" until #10449; use 'DenomAmount' for now", }); - chainHub.registerAsset('uist', { - baseDenom: 'uist', + chainHub.registerAsset('umoo', { + baseDenom: 'umoo', baseName: 'agoric', - brand: ist.brand, + brand: moolah.brand, chainName: 'agoric', }); - // uses uistTransfer mock above - await E(account).transfer(mockDestination, ist.make(10n)); + // uses umooTransfer mock above + await E(account).transfer( + mockDestination, + // moolah.make(10n), // TODO #10449 restore + { denom: 'umoo', value: 10n }, + ); t.log('transfer timeout error recieved and handled from the bridge'); await t.throwsAsync( diff --git a/packages/orchestration/test/exos/local-orchestration-account-kit.test.ts b/packages/orchestration/test/exos/local-orchestration-account-kit.test.ts index c1eed2f6730..afec573e19c 100644 --- a/packages/orchestration/test/exos/local-orchestration-account-kit.test.ts +++ b/packages/orchestration/test/exos/local-orchestration-account-kit.test.ts @@ -9,16 +9,26 @@ import { } from '@agoric/vats/tools/fake-bridge.js'; import { heapVowE as VE } from '@agoric/vow/vat.js'; import { withAmountUtils } from '@agoric/zoe/tools/test-utils.js'; -import type { ChainAddress, AmountArg } from '../../src/orchestration-api.js'; +import type { IBCChannelID } from '@agoric/vats'; +import type { + ChainAddress, + AmountArg, + DenomAmount, +} from '../../src/orchestration-api.js'; import { maxClockSkew } from '../../src/utils/cosmos.js'; import { NANOSECONDS_PER_SECOND } from '../../src/utils/time.js'; import { buildVTransferEvent } from '../../tools/ibc-mocks.js'; import { UNBOND_PERIOD_SECONDS } from '../ibc-mocks.js'; import { commonSetup } from '../supports.js'; import { prepareMakeTestLOAKit } from './make-test-loa-kit.js'; +import fetchedChainInfo from '../../src/fetched-chain-info.js'; +import type { IBCMsgTransferOptions } from '../../src/cosmos-api.js'; +import { PFM_RECEIVER } from '../../src/exos/chain-hub.js'; +import { assetOn } from '../../src/utils/asset.js'; test('deposit, withdraw', async t => { const common = await commonSetup(t); + common.utils.populateChainHub(); const makeTestLOAKit = prepareMakeTestLOAKit(t, common); const account = await makeTestLOAKit(); @@ -64,6 +74,7 @@ test('deposit, withdraw', async t => { test('delegate, undelegate', async t => { const common = await commonSetup(t); + common.utils.populateChainHub(); const makeTestLOAKit = prepareMakeTestLOAKit(t, common); const account = await makeTestLOAKit(); @@ -100,6 +111,7 @@ test('delegate, undelegate', async t => { test('transfer', async t => { const common = await commonSetup(t); + common.utils.populateChainHub(); const makeTestLOAKit = prepareMakeTestLOAKit(t, common); const account = await makeTestLOAKit(); @@ -108,12 +120,12 @@ test('transfer', async t => { const { brands: { bld: stake }, mocks: { transferBridge }, - utils, + utils: { inspectLocalBridge, pourPayment }, } = common; t.truthy(account, 'account is returned'); - const oneHundredStakePmt = await utils.pourPayment(stake.units(100)); + const oneHundredStakePmt = await pourPayment(stake.units(100)); t.log('deposit 100 bld to account'); await VE(account).deposit(oneHundredStakePmt); @@ -127,7 +139,6 @@ test('transfer', async t => { value: 'cosmos1pleab', encoding: 'bech32', }; - const sourceChannel = 'channel-5'; // observed in toBridge VLOCALCHAIN_EXECUTE_TX sourceChannel, confirmed via fetched-chain-info.js /** The running tally of transfer messages that were sent over the bridge */ let lastSequence = 0n; @@ -142,7 +153,7 @@ test('transfer', async t => { const startTransfer = async ( amount: AmountArg, dest: ChainAddress, - opts = {}, + opts: IBCMsgTransferOptions = {}, ) => { const transferP = VE(account).transfer(dest, amount, opts); lastSequence += 1n; @@ -163,16 +174,15 @@ test('transfer', async t => { buildVTransferEvent({ receiver: destination.value, sender, - sourceChannel, + sourceChannel: + fetchedChainInfo.agoric.connections[destination.chainId].transferChannel + .channelId, sequence: lastSequence, }), ); const transferRes = await transferP; - t.true( - transferRes === undefined, - 'Successful transfer returns Promise.', - ); + t.true(transferRes === undefined, 'Successful transfer returns Vow.'); await t.throwsAsync( ( @@ -194,7 +204,7 @@ test('transfer', async t => { // XXX dev has to know not to startTransfer here await t.throwsAsync( VE(account).transfer(unknownDestination, { denom: 'ubld', value: 1n }), - { message: /connection not found: agoric-3<->fakenet/ }, + { message: 'no connection info found for "agoric-3"<->"fakenet"' }, 'cannot create transfer msg with unknown chainId', ); @@ -203,11 +213,13 @@ test('transfer', async t => { * @param amount * @param dest * @param opts + * @param sourceChannel */ const doTransfer = async ( amount: AmountArg, dest: ChainAddress, - opts = {}, + opts: IBCMsgTransferOptions = {}, + sourceChannel?: IBCChannelID, ) => { const { transferP: promise } = await startTransfer(amount, dest, opts); // simulate incoming message so that promise resolves @@ -215,20 +227,33 @@ test('transfer', async t => { buildVTransferEvent({ receiver: dest.value, sender, - sourceChannel, + sourceChannel: + sourceChannel || + fetchedChainInfo.agoric.connections[dest.chainId].transferChannel + .channelId, sequence: lastSequence, }), ); return promise; }; + const lastestTxMsg = () => { + const tx = inspectLocalBridge().at(-1); + if (tx.type !== 'VLOCALCHAIN_EXECUTE_TX') { + throw new Error('last message was not VLOCALCHAIN_EXECUTE_TX'); + } + return tx.messages[0]; + }; + await t.notThrowsAsync( doTransfer({ denom: 'ubld', value: 10n }, destination, { memo: 'hello', }), 'can create transfer msg with memo', ); - // TODO, intercept/spy the bridge message to see that it has a memo + t.like(lastestTxMsg(), { + memo: 'hello', + }); await t.notThrowsAsync( doTransfer({ denom: 'ubld', value: 10n }, destination, { @@ -244,10 +269,63 @@ test('transfer', async t => { }), 'accepts custom timeoutHeight', ); + + const [uusdcOnAgoric] = assetOn( + 'uusdc', + 'noble', + undefined, + 'agoric', + fetchedChainInfo, + ); + const dydxDest: ChainAddress = { + chainId: 'dydx-mainnet-1', + encoding: 'bech32', + value: 'dydx1test', + }; + const aDenomAmount: DenomAmount = { + denom: uusdcOnAgoric, + value: 100n, + }; + + t.log('Transfer handles multi-hop transfers'); + await t.notThrowsAsync( + doTransfer( + aDenomAmount, + dydxDest, + {}, + fetchedChainInfo.agoric.connections['noble-1'].transferChannel.channelId, + ), + ); + + t.like(lastestTxMsg(), { + receiver: PFM_RECEIVER, + memo: '{"forward":{"receiver":"dydx1test","port":"transfer","channel":"channel-33","retries":3,"timeout":"10min"}}', + }); + + t.log('accepts pfm `forwardOpts`'); + await t.notThrowsAsync( + doTransfer( + aDenomAmount, + dydxDest, + { + forwardOpts: { + timeout: '999min', + }, + }, + fetchedChainInfo.agoric.connections['noble-1'].transferChannel.channelId, + ), + ); + + t.like(JSON.parse(lastestTxMsg().memo), { + forward: { + timeout: '999min', + }, + }); }); test('monitor transfers', async t => { const common = await commonSetup(t); + common.utils.populateChainHub(); const makeTestLOAKit = prepareMakeTestLOAKit(t, common); const account = await makeTestLOAKit(); const { @@ -292,6 +370,7 @@ test('monitor transfers', async t => { test('send', async t => { const common = await commonSetup(t); + common.utils.populateChainHub(); const makeTestLOAKit = prepareMakeTestLOAKit(t, common); const account = await makeTestLOAKit(); t.truthy(account, 'account is returned'); @@ -352,6 +431,7 @@ test('send', async t => { test('getBalance', async t => { const common = await commonSetup(t); + common.utils.populateChainHub(); const makeTestLOAKit = prepareMakeTestLOAKit(t, common); const account = await makeTestLOAKit(); t.truthy(account, 'account is returned'); @@ -406,6 +486,7 @@ test('getBalance', async t => { test('getBalances', async t => { const common = await commonSetup(t); + common.utils.populateChainHub(); const makeTestLOAKit = prepareMakeTestLOAKit(t, common); const account = await makeTestLOAKit(); t.truthy(account, 'account is returned'); diff --git a/packages/orchestration/test/exos/make-test-coa-kit.ts b/packages/orchestration/test/exos/make-test-coa-kit.ts index 6042091b31a..f1b63c50550 100644 --- a/packages/orchestration/test/exos/make-test-coa-kit.ts +++ b/packages/orchestration/test/exos/make-test-coa-kit.ts @@ -83,10 +83,6 @@ export const prepareMakeTestCOAKit = ( timer, }, ); - - t.log('register Agoric chain and BLD in ChainHub'); - utils.registerAgoricBld(); - return holder; }; }; diff --git a/packages/orchestration/test/exos/make-test-loa-kit.ts b/packages/orchestration/test/exos/make-test-loa-kit.ts index ee718c5a997..8bb57055d0b 100644 --- a/packages/orchestration/test/exos/make-test-loa-kit.ts +++ b/packages/orchestration/test/exos/make-test-loa-kit.ts @@ -63,9 +63,6 @@ export const prepareMakeTestLOAKit = ( }), storageNode: storageNode.makeChildNode(address), }); - - t.log('register Agoric chain and BLD in ChainHub'); - utils.registerAgoricBld(); return account; }; }; diff --git a/packages/orchestration/test/exos/portfolio-holder-kit.test.ts b/packages/orchestration/test/exos/portfolio-holder-kit.test.ts index a498b9b0c2c..b456399b57c 100644 --- a/packages/orchestration/test/exos/portfolio-holder-kit.test.ts +++ b/packages/orchestration/test/exos/portfolio-holder-kit.test.ts @@ -8,6 +8,7 @@ import { prepareMakeTestCOAKit } from './make-test-coa-kit.js'; test('portfolio holder kit behaviors', async t => { const common = await commonSetup(t); + common.utils.populateChainHub(); const { rootZone, storage, vowTools } = common.bootstrap; const storageNode = storage.rootNode.makeChildNode('accounts'); diff --git a/packages/orchestration/test/snapshots/exports.test.ts.md b/packages/orchestration/test/snapshots/exports.test.ts.md index f4a36635d08..22bf7e69d2d 100644 --- a/packages/orchestration/test/snapshots/exports.test.ts.md +++ b/packages/orchestration/test/snapshots/exports.test.ts.md @@ -23,6 +23,7 @@ Generated by [AVA](https://avajs.dev). 'DenomInfoShape', 'DenomShape', 'ForwardInfoShape', + 'ForwardOptsShape', 'IBCChannelIDShape', 'IBCChannelInfoShape', 'IBCConnectionIDShape', diff --git a/packages/orchestration/test/snapshots/exports.test.ts.snap b/packages/orchestration/test/snapshots/exports.test.ts.snap index 3d9da073d5a..613327cd832 100644 Binary files a/packages/orchestration/test/snapshots/exports.test.ts.snap and b/packages/orchestration/test/snapshots/exports.test.ts.snap differ diff --git a/packages/orchestration/test/supports.ts b/packages/orchestration/test/supports.ts index 6486a7ffbaa..2d8a85cb056 100644 --- a/packages/orchestration/test/supports.ts +++ b/packages/orchestration/test/supports.ts @@ -26,6 +26,9 @@ import { prepareCosmosInterchainService } from '../src/exos/cosmos-interchain-se import fetchedChainInfo from '../src/fetched-chain-info.js'; import { buildVTransferEvent } from '../tools/ibc-mocks.js'; import { setupFakeNetwork } from './network-fakes.js'; +import { withChainCapabilities } from '../src/chain-capabilities.js'; +import { registerChainsAndAssets } from '../src/utils/chain-hub-helper.js'; +import { assetOn } from '../src/utils/asset.js'; export { makeFakeLocalchainBridge, @@ -168,22 +171,33 @@ export const commonSetup = async (t: ExecutionContext) => { vowTools, ); + /** add `pfmEnabled` to chainInfo */ + const chainInfoWithCaps = withChainCapabilities(fetchedChainInfo); + + /** for registration with `ChainHub` */ + const commonAssetInfo = harden([ + assetOn('ubld', 'agoric', bldSansMint.brand), + assetOn('uist', 'agoric', istSansMint.brand), + assetOn('uist', 'agoric', undefined, 'cosmoshub', chainInfoWithCaps), + assetOn('uusdc', 'noble', undefined, 'agoric', chainInfoWithCaps), + assetOn('uatom', 'cosmoshub', undefined, 'agoric', chainInfoWithCaps), + assetOn('uusdc', 'noble', undefined, 'dydx', chainInfoWithCaps), + ]); + /** - * Register BLD if it's not already registered. - * Does not work with `withOrchestration` contracts, as these have their own - * ChainHub. Use `ChainHubAdmin` instead. + * Register known chains an assets into the test context's `ChainHub`. + * + * For contract tests with contracts that use `withOrchestration`, access + * `chainInfo` and `assetInfo` from `commonPrivateArgs` and register in the + * contract's ChainHub with `registerChainsAndAssets`. */ - const registerAgoricBld = () => { - if (!chainHub.getAsset('ubld', 'agoric')) { - chainHub.registerChain('agoric', fetchedChainInfo.agoric); - chainHub.registerAsset('ubld', { - chainName: 'agoric', - baseName: 'agoric', - baseDenom: 'ubld', - brand: bld.brand, - }); - } - }; + const populateChainHub = () => + registerChainsAndAssets( + chainHub, + harden({ BLD: bldSansMint.brand, IST: istSansMint.brand }), + chainInfoWithCaps, + commonAssetInfo, + ); return { bootstrap: { @@ -214,6 +228,8 @@ export const commonSetup = async (t: ExecutionContext) => { storageNode: storage.rootNode, marshaller, timerService: timer, + chainInfo: withChainCapabilities(fetchedChainInfo), + assetInfo: harden(commonAssetInfo), }, facadeServices: { agoricNames, @@ -227,7 +243,7 @@ export const commonSetup = async (t: ExecutionContext) => { inspectLocalBridge: () => harden([...localBridgeMessages]), inspectDibcBridge: () => E(ibcBridge).inspectDibcBridge(), inspectBankBridge: () => harden([...bankBridgeMessages]), - registerAgoricBld, + populateChainHub, transmitTransferAck, }, };