From 05fe5154de7cccb0c606dcc76c058badc5526cdb Mon Sep 17 00:00:00 2001 From: 0xPatrick Date: Wed, 27 Nov 2024 13:55:28 -0500 Subject: [PATCH 1/5] feat: `assetOn` `DenomDetail` helper Co-authored-by: Dan Connolly --- packages/orchestration/src/utils/asset.js | 34 +++++++++++++++++++ .../src/utils/chain-hub-helper.js | 2 ++ 2 files changed, 36 insertions(+) create mode 100644 packages/orchestration/src/utils/asset.js diff --git a/packages/orchestration/src/utils/asset.js b/packages/orchestration/src/utils/asset.js new file mode 100644 index 00000000000..3a39da7135b --- /dev/null +++ b/packages/orchestration/src/utils/asset.js @@ -0,0 +1,34 @@ +import { denomHash } from './denomHash.js'; + +/** + * @import {ChainHub, CosmosChainInfo, Denom, DenomDetail} from '../types.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 {string} [chainName] + * @param {Record} [infoOf] + * @param {string} [brandKey] + * @returns {[string, DenomDetail & { brandKey?: string }]} + */ +export const assetOn = (baseDenom, baseName, chainName, infoOf, brandKey) => { + if (!chainName) { + return [baseDenom, { baseName, chainName: baseName, baseDenom }]; + } + if (!infoOf) throw Error(`must provide infoOf`); + const issuerInfo = infoOf[baseName]; + const holdingInfo = infoOf[chainName]; + if (!holdingInfo) throw Error(`${chainName} missing`); + if (!holdingInfo.connections) + throw Error(`connections missing for ${chainName}`); + const { channelId } = + holdingInfo.connections[issuerInfo.chainId].transferChannel; + const denom = `ibc/${denomHash({ denom: baseDenom, channelId })}`; + return [denom, { baseName, chainName, baseDenom, brandKey }]; +}; diff --git a/packages/orchestration/src/utils/chain-hub-helper.js b/packages/orchestration/src/utils/chain-hub-helper.js index 9e75248c6e3..1ae2e0ffaf8 100644 --- a/packages/orchestration/src/utils/chain-hub-helper.js +++ b/packages/orchestration/src/utils/chain-hub-helper.js @@ -7,6 +7,8 @@ * * If either is not provided, registration will be skipped. * + * TODO #10580 remove 'brandKey' in favor of `LegibleCapData` + * * @param {ChainHub} chainHub * @param {Record>} brands * @param {Record | undefined} chainInfo From a6c1e9ec5001630f114e16d307b3ce09415bea77 Mon Sep 17 00:00:00 2001 From: 0xPatrick Date: Wed, 27 Nov 2024 14:29:54 -0500 Subject: [PATCH 2/5] types: `ForwardInfo` - json structure of pfm memo --- packages/orchestration/src/cosmos-api.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/packages/orchestration/src/cosmos-api.ts b/packages/orchestration/src/cosmos-api.ts index 298878634b9..060e99f6409 100644 --- a/packages/orchestration/src/cosmos-api.ts +++ b/packages/orchestration/src/cosmos-api.ts @@ -23,6 +23,7 @@ import type { Port } from '@agoric/network'; import type { IBCChannelID, IBCConnectionID, + IBCPortID, VTransferIBCEvent, } from '@agoric/vats'; import type { @@ -352,3 +353,23 @@ export type CosmosChainAccountMethods = export type ICQQueryFunction = ( msgs: JsonSafe[], ) => Promise[]>; + +/** + * Message structure for PFM memo + * + * @see {@link https://github.com/cosmos/chain-registry/blob/58b603bbe01f70e911e3ad2bdb6b90c4ca665735/_memo_keys/ICS20_memo_keys.json#L38-L60} + */ +export interface ForwardInfo { + forward: { + receiver: ChainAddress['value']; + port: IBCPortID; + channel: IBCChannelID; + /** e.g. '10min' */ + timeout: string; + /** default is 3? */ + retries: number; + next?: { + forward: ForwardInfo; + }; + }; +} From 9a654781d53576ae0b3d1fa37f7a96579bfda848 Mon Sep 17 00:00:00 2001 From: 0xPatrick Date: Wed, 27 Nov 2024 19:17:30 -0500 Subject: [PATCH 3/5] fix: `brandKey` not part of `DenomDetail` - also, dont iterate over connections if there arent any --- .../snapshots/fast-usdc.contract.test.ts.md | 2 -- .../snapshots/fast-usdc.contract.test.ts.snap | Bin 5662 -> 5637 bytes .../src/utils/chain-hub-helper.js | 9 +++++---- 3 files changed, 5 insertions(+), 6 deletions(-) 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 42cfa097928..62804cfa992 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 @@ -539,14 +539,12 @@ Generated by [AVA](https://avajs.dev). 'ibc/498A0751C798A0D9A389AA3691123DADA57DAA4FE165D5C75894505B876BA6E4': { baseDenom: 'uusdc', baseName: 'noble', - brandKey: undefined, chainName: 'osmosis', }, 'ibc/FE98AAD68F02F03565E9FA39A5E627946699B2B07115889ED812D8BA639576A9': { baseDenom: 'uusdc', baseName: 'noble', brand: Object @Alleged: USDC brand {}, - brandKey: 'USDC', chainName: 'agoric', }, uusdc: { 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 dcb9a05cb2547cce336ef8a28f82ece5c24e5a80..084687b3bab0cbb2441b180c061d6ebbda934d4e 100644 GIT binary patch literal 5637 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 literal 5662 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 E01pQU2><{9 diff --git a/packages/orchestration/src/utils/chain-hub-helper.js b/packages/orchestration/src/utils/chain-hub-helper.js index 1ae2e0ffaf8..306a5ecef56 100644 --- a/packages/orchestration/src/utils/chain-hub-helper.js +++ b/packages/orchestration/src/utils/chain-hub-helper.js @@ -29,7 +29,7 @@ export const registerChainsAndAssets = ( for (const [chainName, allInfo] of Object.entries(chainInfo)) { const { connections, ...info } = allInfo; chainHub.registerChain(chainName, info); - conns[info.chainId] = connections; + if (connections) conns[info.chainId] = connections; } const registeredPairs = new Set(); for (const [pChainId, connInfos] of Object.entries(conns)) { @@ -48,9 +48,10 @@ export const registerChainsAndAssets = ( return; } for (const [denom, info] of Object.entries(assetInfo)) { - const infoWithBrand = info.brandKey - ? { ...info, brand: brands[info.brandKey] } - : info; + const { brandKey, ...rest } = info; + const infoWithBrand = brandKey + ? { ...rest, brand: brands[brandKey] } + : rest; chainHub.registerAsset(denom, infoWithBrand); } }; From 0215b6f172e523a813bfbf6234991856ec60601c Mon Sep 17 00:00:00 2001 From: 0xPatrick Date: Tue, 26 Nov 2024 10:57:13 -0500 Subject: [PATCH 4/5] feat: `chainHub.makeTransferRoute` - chainHub helper to return .transfer details for ibc transfer that can have multiple hops (via pfm) --- packages/orchestration/src/cosmos-api.ts | 26 ++ packages/orchestration/src/exos/chain-hub.js | 146 ++++++- packages/orchestration/src/typeGuards.js | 34 +- .../orchestration/test/exos/chain-hub.test.ts | 372 +++++++++++++++++- 4 files changed, 573 insertions(+), 5 deletions(-) diff --git a/packages/orchestration/src/cosmos-api.ts b/packages/orchestration/src/cosmos-api.ts index 060e99f6409..cbbe4658760 100644 --- a/packages/orchestration/src/cosmos-api.ts +++ b/packages/orchestration/src/cosmos-api.ts @@ -35,7 +35,9 @@ import type { RemoteIbcAddress, } from '@agoric/vats/tools/ibc-utils.js'; import type { QueryDelegationTotalRewardsResponse } from '@agoric/cosmic-proto/cosmos/distribution/v1beta1/query.js'; +import type { Coin } from '@agoric/cosmic-proto/cosmos/base/v1beta1/coin.js'; import type { AmountArg, ChainAddress, Denom, DenomAmount } from './types.js'; +import { PFM_RECEIVER } from './exos/chain-hub.js'; /** An address for a validator on some blockchain, e.g., cosmos, eth, etc. */ export type CosmosValidatorAddress = ChainAddress & { @@ -373,3 +375,27 @@ export interface ForwardInfo { }; }; } + +/** + * Object used to help build MsgTransfer parameters for IBC transfers. + * + * If `forwardInfo` is present: + * - it must be stringified and provided as the `memo` field value for + * use with `MsgTransfer`. + * - `receiver` will be set to `"pfm"` - purposely invalid bech32. see {@link https://github.com/cosmos/ibc-apps/blob/26f3ad8f58e4ffc7769c6766cb42b954181dc100/middleware/packet-forward-middleware/README.md#minimal-example---chain-forward-a-b-c} + */ +export type TransferRoute = { + /** typically, `transfer` */ + sourcePort: string; + sourceChannel: IBCChannelID; + token: Coin; +} & ( + | { + receiver: typeof PFM_RECEIVER; + /** contains PFM forwarding info */ + forwardInfo: ForwardInfo; + } + | { + receiver: string; + } +); diff --git a/packages/orchestration/src/exos/chain-hub.js b/packages/orchestration/src/exos/chain-hub.js index 39cd3626d83..f74d9eda964 100644 --- a/packages/orchestration/src/exos/chain-hub.js +++ b/packages/orchestration/src/exos/chain-hub.js @@ -6,8 +6,12 @@ import { BrandShape } from '@agoric/ertp/src/typeGuards.js'; import { VowShape } from '@agoric/vow'; import { ChainAddressShape, + CoinShape, CosmosChainInfoShape, + DenomAmountShape, DenomDetailShape, + ForwardInfoShape, + IBCChannelIDShape, IBCConnectionInfoShape, } from '../typeGuards.js'; import { getBech32Prefix } from '../utils/address.js'; @@ -16,12 +20,15 @@ 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, IBCConnectionInfo} from '../cosmos-api.js'; + * @import {CosmosAssetInfo, CosmosChainInfo, ForwardInfo, IBCConnectionInfo, TransferRoute} from '../cosmos-api.js'; * @import {ChainInfo, KnownChains} from '../chain-info.js'; - * @import {ChainAddress, Denom} from '../orchestration-api.js'; - * @import {Remote} from '@agoric/internal'; + * @import {ChainAddress, Denom, DenomAmount} from '../orchestration-api.js'; + * @import {Remote, TypedPattern} from '@agoric/internal'; */ +/** receiver address value for ibc transfers that involve PFM */ +export const PFM_RECEIVER = /** @type {const} */ ('pfm'); + /** * If K matches a known chain, narrow the type from generic ChainInfo * @@ -167,6 +174,26 @@ const ChainIdArgShape = M.or( ), ); +// TODO #9324 determine timeout defaults +const DefaultPfmTimeoutOpts = harden( + /** @type {const} */ ({ + retries: 3, + timeout: '10min', + }), +); + +/** @type {TypedPattern} */ +export const TransferRouteShape = M.splitRecord( + { + sourcePort: M.string(), + sourceChannel: IBCChannelIDShape, + token: CoinShape, + receiver: M.string(), + }, + { forwardInfo: ForwardInfoShape }, + {}, +); + const ChainHubI = M.interface('ChainHub', { registerChain: M.call(M.string(), CosmosChainInfoShape).returns(), getChainInfo: M.call(M.string()).returns(VowShape), @@ -181,6 +208,12 @@ const ChainHubI = M.interface('ChainHub', { getAsset: M.call(M.string()).returns(M.or(DenomDetailShape, M.undefined())), 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(), + }) + .returns(M.or(M.undefined(), TransferRouteShape)), }); /** @@ -454,6 +487,113 @@ export const makeChainHub = (zone, agoricNames, vowTools) => { encoding: /** @type {const} */ ('bech32'), }); }, + /** + * Determine the transfer route for a destination and amount given the + * current holding chain. + * + * XXX consider accepting AmountArg #10449 + * + * @param {ChainAddress} destination + * @param {DenomAmount} denomAmount + * @param {string} holdingChainName + * @param {Pick} [forwardOpts] + * @returns {TransferRoute} single hop, multi hop + * @throws {Error} if unable to determine route + */ + makeTransferRoute(destination, denomAmount, holdingChainName, forwardOpts) { + chainInfos.has(holdingChainName) || + Fail`chain info not found for holding chain: ${q(holdingChainName)}`; + + const denomDetail = chainHub.getAsset(denomAmount.denom); + denomDetail || + Fail`no denom detail for: ${q(denomAmount.denom)}. ensure it is registered in chainHub.`; + + const { baseName, chainName } = /** @type {DenomDetail} */ (denomDetail); + chainName === holdingChainName || + Fail`cannot transfer asset ${q(denomAmount.denom)}. held on ${q(chainName)} not ${q(holdingChainName)}.`; + + // currently unreachable since we can't register an asset before a chain + chainInfos.has(baseName) || + Fail`chain info not found for issuing chain: ${q(baseName)}`; + + const { chainId: baseChainId, pfmEnabled } = chainInfos.get(baseName); + + const holdingChainId = chainInfos.get(holdingChainName).chainId; + + // asset is transferring to or from the issuing chain, return direct route + if ( + baseChainId === destination.chainId || + baseName === holdingChainName + ) { + // TODO use getConnectionInfo once its sync + const connKey = connectionKey(holdingChainId, destination.chainId); + connectionInfos.has(connKey) || + Fail`no connection info found for ${q(connKey)}`; + + const { transferChannel } = denormalizeConnectionInfo( + holdingChainId, // from chain (primary) + destination.chainId, // to chain (counterparty) + connectionInfos.get(connKey), + ); + return harden({ + sourcePort: transferChannel.portId, + sourceChannel: transferChannel.channelId, + token: { + amount: String(denomAmount.value), + denom: denomAmount.denom, + }, + receiver: destination.value, + }); + } + + // asset is issued on a 3rd chain, attempt pfm route + pfmEnabled || Fail`pfm not enabled on issuing chain: ${q(baseName)}`; + + // TODO use getConnectionInfo once its sync + const currToIssuerKey = connectionKey(holdingChainId, baseChainId); + connectionInfos.has(currToIssuerKey) || + Fail`no connection info found for ${q(currToIssuerKey)}`; + + const issuerToDestKey = connectionKey(baseChainId, destination.chainId); + connectionInfos.has(issuerToDestKey) || + Fail`no connection info found for ${q(issuerToDestKey)}`; + + const currToIssuer = denormalizeConnectionInfo( + holdingChainId, + baseChainId, + connectionInfos.get(currToIssuerKey), + ); + const issuerToDest = denormalizeConnectionInfo( + baseChainId, + destination.chainId, + connectionInfos.get(issuerToDestKey), + ); + + /** @type {ForwardInfo} */ + const forwardInfo = harden({ + forward: { + receiver: destination.value, + port: issuerToDest.transferChannel.portId, + channel: issuerToDest.transferChannel.channelId, + ...DefaultPfmTimeoutOpts, + ...forwardOpts, + }, + }); + return harden({ + sourcePort: currToIssuer.transferChannel.portId, + sourceChannel: currToIssuer.transferChannel.channelId, + token: { + amount: String(denomAmount.value), + denom: denomAmount.denom, + }, + /** + * purposely using invalid bech32 + * {@link https://github.com/cosmos/ibc-apps/blob/26f3ad8f58e4ffc7769c6766cb42b954181dc100/middleware/packet-forward-middleware/README.md#minimal-example---chain-forward-a-b-c} + */ + receiver: PFM_RECEIVER, + forwardInfo, + }); + }, }); return chainHub; diff --git a/packages/orchestration/src/typeGuards.js b/packages/orchestration/src/typeGuards.js index 721345c0d9f..f97f2bb24c0 100644 --- a/packages/orchestration/src/typeGuards.js +++ b/packages/orchestration/src/typeGuards.js @@ -4,9 +4,10 @@ import { M } from '@endo/patterns'; /** * @import {TypedPattern} from '@agoric/internal'; - * @import {ChainAddress, CosmosAssetInfo, Chain, ChainInfo, CosmosChainInfo, DenomAmount, DenomInfo, AmountArg, CosmosValidatorAddress, OrchestrationPowers} from './types.js'; + * @import {ChainAddress, CosmosAssetInfo, Chain, ChainInfo, CosmosChainInfo, DenomAmount, DenomInfo, AmountArg, CosmosValidatorAddress, OrchestrationPowers, ForwardInfo} 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'; * @import {TypedJson} from '@agoric/cosmic-proto'; * @import {DenomDetail} from './exos/chain-hub.js'; */ @@ -112,6 +113,14 @@ export const ChainInfoShape = M.splitRecord({ }); export const DenomShape = M.string(); +/** @type {TypedPattern} */ +export const CoinShape = { + /** json-safe stringified bigint */ + amount: M.string(), + denom: DenomShape, +}; +harden(CoinShape); + /** @type {TypedPattern>} */ export const DenomInfoShape = { chain: M.remotable('Chain'), @@ -215,3 +224,26 @@ export const OrchestrationPowersShape = { timerService: M.remotable(), }; harden(OrchestrationPowersShape); + +const ForwardArgsShape = { + receiver: M.string(), + port: 'transfer', + channel: M.string(), + timeout: M.string(), + retries: M.number(), +}; +harden(ForwardArgsShape); + +/** @type {TypedPattern} */ +export const ForwardInfoShape = { + forward: M.splitRecord(ForwardArgsShape, { + /** + * Protocol allows us to recursively include `next` keys, but this only + * supports one. In practice, this is all we currently need. + */ + next: { + forward: ForwardArgsShape, + }, + }), +}; +harden(ForwardInfoShape); diff --git a/packages/orchestration/test/exos/chain-hub.test.ts b/packages/orchestration/test/exos/chain-hub.test.ts index b4f4823be97..b93e4e34464 100644 --- a/packages/orchestration/test/exos/chain-hub.test.ts +++ b/packages/orchestration/test/exos/chain-hub.test.ts @@ -1,4 +1,3 @@ -/* eslint-disable @jessie.js/safe-await-separator -- XXX irrelevant for tests */ import '@agoric/swingset-liveslots/tools/prepare-test-env.js'; import test from '@endo/ses-ava/prepare-endo.js'; @@ -7,6 +6,8 @@ import { prepareSwingsetVowTools } from '@agoric/vow/vat.js'; import { E } from '@endo/far'; import { makeIssuerKit } from '@agoric/ertp'; import { withAmountUtils } from '@agoric/zoe/tools/test-utils.js'; +import { typedJson } from '@agoric/cosmic-proto'; +import { objectMap } from '@endo/patterns'; import { makeChainHub, registerAssets } from '../../src/exos/chain-hub.js'; import { provideFreshRootZone } from '../durability.js'; import { @@ -19,6 +20,10 @@ import type { IBCConnectionInfo, } from '../../src/cosmos-api.js'; import { assets as assetFixture } from '../assets.fixture.js'; +import { registerChainsAndAssets } from '../../src/utils/chain-hub-helper.js'; +import { assetOn } from '../../src/utils/asset.js'; +import { withChainCapabilities } from '../../src/chain-capabilities.js'; +import type { ChainAddress, DenomAmount } from '../../src/orchestration-api.js'; // fresh state for each test const setup = () => { @@ -191,3 +196,368 @@ test('makeChainAddress', async t => { message: 'Missing prefix for "1notbech32"', }); }); + +const [uusdcOnAgoric, agDetail] = assetOn( + 'uusdc', + 'noble', + 'agoric', + knownChains, +); +const [uusdcOnOsmosis, osDetail] = assetOn( + 'uusdc', + 'noble', + 'osmosis', + knownChains, +); + +test('makeTransferRoute - to issuing chain', async t => { + const { chainHub } = setup(); + registerChainsAndAssets( + chainHub, + {}, + withChainCapabilities(knownChains), // adds pfmEnabled + harden({ [uusdcOnAgoric]: agDetail, [uusdcOnOsmosis]: osDetail }), + ); + + const dest: ChainAddress = chainHub.makeChainAddress('noble1234'); + { + // 100 USDC on agoric -> noble + const amt: DenomAmount = harden({ denom: uusdcOnAgoric, value: 100n }); + t.deepEqual(chainHub.makeTransferRoute(dest, amt, 'agoric'), { + receiver: 'noble1234', + sourceChannel: 'channel-62', + sourcePort: 'transfer', + token: { + amount: '100', + denom: + 'ibc/FE98AAD68F02F03565E9FA39A5E627946699B2B07115889ED812D8BA639576A9', + }, + }); + } + { + // 100 USDC on osmosis -> noble + const amt: DenomAmount = harden({ denom: uusdcOnOsmosis, value: 100n }); + t.deepEqual(chainHub.makeTransferRoute(dest, amt, 'osmosis'), { + receiver: 'noble1234', + sourceChannel: 'channel-750', + sourcePort: 'transfer', + token: { + amount: '100', + denom: + 'ibc/498A0751C798A0D9A389AA3691123DADA57DAA4FE165D5C75894505B876BA6E4', + }, + }); + } +}); + +test('makeTransferRoute - from issuing chain', async t => { + const { chainHub } = setup(); + + registerChainsAndAssets( + chainHub, + {}, + withChainCapabilities(knownChains), // adds pfmEnabled + harden( + Object.fromEntries([ + assetOn('uist', 'agoric'), + assetOn('uosmo', 'osmosis'), + ]), + ), + ); + + const dest: ChainAddress = chainHub.makeChainAddress('noble1234'); + { + // IST on agoric -> noble + const amt: DenomAmount = harden({ denom: 'uist', value: 100n }); + t.deepEqual(chainHub.makeTransferRoute(dest, amt, 'agoric'), { + receiver: 'noble1234', + sourceChannel: 'channel-62', + sourcePort: 'transfer', + token: { + amount: '100', + denom: 'uist', + }, + }); + } + { + // OSMO on osmosis -> noble + const amt: DenomAmount = harden({ denom: 'uosmo', value: 100n }); + t.deepEqual(chainHub.makeTransferRoute(dest, amt, 'osmosis'), { + receiver: 'noble1234', + sourceChannel: 'channel-750', + sourcePort: 'transfer', + token: { + amount: '100', + denom: 'uosmo', + }, + }); + } +}); + +test('makeTransferRoute - through issuing chain', async t => { + const { chainHub } = setup(); + + registerChainsAndAssets( + chainHub, + {}, + withChainCapabilities(knownChains), // adds pfmEnabled + harden({ [uusdcOnAgoric]: agDetail }), + ); + + const dest: ChainAddress = chainHub.makeChainAddress('osmo1234'); + const amt: DenomAmount = harden({ denom: uusdcOnAgoric, value: 100n }); + + // 100 USDC on agoric -> osmosis + const route = chainHub.makeTransferRoute(dest, amt, 'agoric'); + t.deepEqual(route, { + sourcePort: 'transfer', + sourceChannel: 'channel-62', + token: { + amount: '100', + denom: + 'ibc/FE98AAD68F02F03565E9FA39A5E627946699B2B07115889ED812D8BA639576A9', + }, + receiver: 'pfm', + forwardInfo: { + forward: { + receiver: 'osmo1234', + port: 'transfer', + channel: 'channel-1', + retries: 3, + timeout: '10min', + }, + }, + }); + + // use TransferRoute to build a MsgTransfer + if (!route || !('forwardInfo' in route)) { + throw new Error('forwardInfo not returned'); // appease tsc... + } + + const { forwardInfo, ...rest } = route; + const transferMsg = typedJson('/ibc.applications.transfer.v1.MsgTransfer', { + ...rest, + memo: JSON.stringify(forwardInfo), + // callers of `.makeTransferRoute` will provide these fields themselves: + sender: 'agoric123', + timeoutHeight: { + revisionHeight: 0n, + revisionNumber: 0n, + }, + timeoutTimestamp: 0n, + }); + t.like(transferMsg, { + memo: '{"forward":{"receiver":"osmo1234","port":"transfer","channel":"channel-1","retries":3,"timeout":"10min"}}', + receiver: 'pfm', + }); +}); + +test('makeTransferRoute - takes forwardOpts', t => { + const { chainHub } = setup(); + + registerChainsAndAssets( + chainHub, + {}, + withChainCapabilities(knownChains), // adds pfmEnabled + harden({ [uusdcOnOsmosis]: osDetail }), + ); + + const dest: ChainAddress = chainHub.makeChainAddress('agoric1234'); + const amt: DenomAmount = harden({ denom: uusdcOnOsmosis, value: 100n }); + const forwardOpts = harden({ + retries: 1, + timeout: '3min', + }); + + // 100 USDC on osmosis -> agoric + const route = chainHub.makeTransferRoute(dest, amt, 'osmosis', forwardOpts); + t.like(route, { + sourceChannel: 'channel-750', + token: { + denom: uusdcOnOsmosis, + }, + forwardInfo: { + forward: { + channel: 'channel-21', + ...forwardOpts, + }, + }, + }); + + // test that typeGuard works + t.throws( + () => + chainHub.makeTransferRoute( + dest, + amt, + 'osmosis', + harden({ + ...forwardOpts, + forward: JSON.stringify('stringified nested forward data'), + }), + ), + { message: /Must not have unexpected properties/ }, + ); +}); + +const nobleDest: ChainAddress = harden({ + value: 'noble1234', + chainId: 'noble-1', + encoding: 'bech32', +}); + +test('makeTransferRoute - no chain info', t => { + const { chainHub } = setup(); + + const amt: DenomAmount = harden({ denom: 'uist', value: 100n }); + t.throws(() => chainHub.makeTransferRoute(nobleDest, amt, 'agoric'), { + message: 'chain info not found for holding chain: "agoric"', + }); +}); + +test('makeTransferRoute - no asset info', t => { + const { chainHub } = setup(); + + registerChainsAndAssets( + chainHub, + {}, + withChainCapabilities(knownChains), // adds pfmEnabled + undefined, // do not supply asset info + ); + + t.throws( + () => + chainHub.makeTransferRoute( + nobleDest, + harden({ denom: 'uist', value: 100n }), + 'agoric', + ), + { + message: + 'no denom detail for: "uist". ensure it is registered in chainHub.', + }, + ); + + t.throws( + () => + chainHub.makeTransferRoute( + nobleDest, + harden({ denom: uusdcOnAgoric, value: 100n }), + 'agoric', + ), + { + message: + 'no denom detail for: "ibc/FE98AAD68F02F03565E9FA39A5E627946699B2B07115889ED812D8BA639576A9". ensure it is registered in chainHub.', + }, + ); +}); + +const knownChainsSansConns = objectMap( + withChainCapabilities(knownChains), + ({ connections, ...rest }) => rest, +); + +test('makeTransferRoute - no connection info single hop', t => { + const { chainHub } = setup(); + + registerChainsAndAssets( + chainHub, + {}, + knownChainsSansConns, // omit connections + harden({ [uusdcOnAgoric]: agDetail }), + ); + + t.throws( + () => + chainHub.makeTransferRoute( + nobleDest, + harden({ denom: uusdcOnAgoric, value: 100n }), + 'agoric', + ), + { message: 'no connection info found for "agoric-3_noble-1"' }, + ); +}); + +test('makeTransferRoute - no connection info multi hop', t => { + const { chainHub } = setup(); + + // only agoric has connection info; osmosis<>noble will be missing + const chainInfo = { ...knownChainsSansConns, agoric: knownChains.agoric }; + registerChainsAndAssets( + chainHub, + {}, + harden(chainInfo), + harden({ [uusdcOnAgoric]: agDetail, [uusdcOnOsmosis]: osDetail }), + ); + + const osmoDest = chainHub.makeChainAddress('osmo1234'); + const agoricDest = chainHub.makeChainAddress('agoric1234'); + + t.throws( + () => + chainHub.makeTransferRoute( + osmoDest, + harden({ denom: uusdcOnAgoric, value: 100n }), + 'agoric', + ), + { message: 'no connection info found for "noble-1_osmosis-1"' }, + ); + + // transfer USDC on osmosis to agoric + t.throws( + () => + chainHub.makeTransferRoute( + agoricDest, + harden({ denom: uusdcOnOsmosis, value: 100n }), + 'osmosis', + ), + { message: 'no connection info found for "noble-1_osmosis-1"' }, + ); +}); + +test('makeTransferRoute - asset not on holding chain', t => { + const { chainHub } = setup(); + + registerChainsAndAssets( + chainHub, + {}, + withChainCapabilities(knownChains), + harden({ [uusdcOnAgoric]: agDetail }), + ); + + // transfer USDC on agoric from osmosis to noble (impossible) + t.throws( + () => + chainHub.makeTransferRoute( + nobleDest, + harden({ denom: uusdcOnAgoric, value: 100n }), + 'osmosis', + ), + { + message: + 'cannot transfer asset "ibc/FE98AAD68F02F03565E9FA39A5E627946699B2B07115889ED812D8BA639576A9". held on "agoric" not "osmosis".', + }, + ); +}); + +test('makeTransferRoute - no PFM path', t => { + const { chainHub } = setup(); + + registerChainsAndAssets( + chainHub, + {}, + knownChains, // intentionally omit pfmEnabled + harden({ [uusdcOnAgoric]: agDetail }), + ); + + // transfer USDC on agoric to osmosis + t.throws( + () => + chainHub.makeTransferRoute( + chainHub.makeChainAddress('osmo1234'), + harden({ denom: uusdcOnAgoric, value: 100n }), + 'agoric', + ), + { message: 'pfm not enabled on issuing chain: "noble"' }, + ); +}); From 1717b2aa7859a26bf6acdb9ba87d8c9cf1f86a2e Mon Sep 17 00:00:00 2001 From: 0xPatrick Date: Wed, 27 Nov 2024 15:06:10 -0500 Subject: [PATCH 5/5] chore: include `ForwardInfoShape` and `CoinShape` in snapshots --- .../orchestration-imports.test.js.md | 48 ++++++++++++++++++ .../orchestration-imports.test.js.snap | Bin 3753 -> 3987 bytes .../test/snapshots/exports.test.ts.md | 2 + .../test/snapshots/exports.test.ts.snap | Bin 662 -> 689 bytes 4 files changed, 50 insertions(+) diff --git a/packages/builders/test/snapshots/orchestration-imports.test.js.md b/packages/builders/test/snapshots/orchestration-imports.test.js.md index 7749e126b16..879f4029b18 100644 --- a/packages/builders/test/snapshots/orchestration-imports.test.js.md +++ b/packages/builders/test/snapshots/orchestration-imports.test.js.md @@ -85,6 +85,14 @@ Generated by [AVA](https://avajs.dev). }, ], }, + CoinShape: { + amount: Object @match:string { + payload: [], + }, + denom: Object @match:string { + payload: [], + }, + }, CosmosAssetInfoShape: Object @match:splitRecord { payload: [ { @@ -270,6 +278,46 @@ Generated by [AVA](https://avajs.dev). DenomShape: Object @match:string { payload: [], }, + ForwardInfoShape: { + forward: Object @match:splitRecord { + payload: [ + { + channel: Object @match:string { + payload: [], + }, + port: 'transfer', + receiver: Object @match:string { + payload: [], + }, + retries: Object @match:kind { + payload: 'number', + }, + timeout: Object @match:string { + payload: [], + }, + }, + { + next: { + forward: { + channel: Object @match:string { + payload: [], + }, + port: 'transfer', + receiver: Object @match:string { + payload: [], + }, + retries: Object @match:kind { + payload: 'number', + }, + timeout: Object @match:string { + payload: [], + }, + }, + }, + }, + ], + }, + }, IBCChannelIDShape: 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 e3536ba0aecf546bded44c857e96d8a48817649f..dba58251741462f8c1e1c312743d2c4078823f05 100644 GIT binary patch literal 3987 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 literal 3753 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 TE(gRxtMC5{XXopq=r8~P1@TL_ diff --git a/packages/orchestration/test/snapshots/exports.test.ts.md b/packages/orchestration/test/snapshots/exports.test.ts.md index c57931aa11b..f4a36635d08 100644 --- a/packages/orchestration/test/snapshots/exports.test.ts.md +++ b/packages/orchestration/test/snapshots/exports.test.ts.md @@ -14,6 +14,7 @@ Generated by [AVA](https://avajs.dev). 'AnyNatAmountsRecord', 'ChainAddressShape', 'ChainInfoShape', + 'CoinShape', 'CosmosAssetInfoShape', 'CosmosChainInfoShape', 'DelegationShape', @@ -21,6 +22,7 @@ Generated by [AVA](https://avajs.dev). 'DenomDetailShape', 'DenomInfoShape', 'DenomShape', + 'ForwardInfoShape', 'IBCChannelIDShape', 'IBCChannelInfoShape', 'IBCConnectionIDShape', diff --git a/packages/orchestration/test/snapshots/exports.test.ts.snap b/packages/orchestration/test/snapshots/exports.test.ts.snap index acdc30319ea66e263ba980fca052e20ce47d0ff2..3d9da073d5a8c310ae5fb16b5b2bbbcd5cc14d37 100644 GIT binary patch literal 689 zcmV;i0#5xwRzVs(J6FWPCfp5S^=mHbuz@|=nlcnc(&-?m)ADz<>a62=JKz zKL{`p0Jj665dbUzUIoDW0QeFB=K-)40{26p69TV8;8O^E4}o7Huvr4`mVjp^;AIJT zTLQk8fS-TPJ7u6oCaRM~Q7s?>SLkEuzeUu@a}3w17J?}~}c_(Yu>nr0p`>+V_* zmztQf`b?pF+BN6CC)I!|cIoeVH|*wa94Ax=o;B+pQgKVWfQ=b_qj-nCt9@1(ye@NeU>YIqjrb XYEtg79hb}z=56pd&!i?GZ3O@T#M(L% literal 662 zcmV;H0%`q0RzVZ(wHnwQ*mbZ4G+2aX7K2sm>~b6;Byq?JM z18)DSfJBZo&Jy#8`brCjVqd43@=PW|Xfh_KQ}L~oq%99rkXK#4+*D?$Okxb#b82cr zU0iG0salH$bjWR{WB_IySZtA0!warG8;TZLo(%#q=Py)>b4|>zxx?7dB$b`90gKRQop$CM5&rntD%~#w&>hid%M}yT$%&YoxHH&Eb9)= zx0?@lO}JA_E?n>EeoDsb^8NSJ5$Ui@!^w@#$oAAoivP{+a480)+s-cgBlonj>Q+!_ z#lKZI8eg7-rI`t4?h>RqlCPTGHjQ`=p;`ec#kIKQm2- wnX#nHvL=zD{0I5YHXU3!%4}MqCeL{<8)P;k&$j$#7A%