From 51e7a9c3e3fb2cde44db2ffce817f353a17e76a3 Mon Sep 17 00:00:00 2001 From: 0xPatrick Date: Fri, 29 Nov 2024 16:38:20 -0500 Subject: [PATCH 1/4] feat: assetInfo as array of entries - to facililate registering the same denom across multiple chains in `ChainHub` - denoms are unique to a chain, but not all chains --- .../scripts/fast-usdc/init-fast-usdc.js | 28 ++++++++++------- packages/fast-usdc/src/fast-usdc.contract.js | 5 ++-- packages/fast-usdc/src/fast-usdc.start.js | 10 +++++-- packages/fast-usdc/test/supports.ts | 12 ++++---- .../src/examples/send-anywhere.contract.js | 2 +- .../src/proposals/start-send-anywhere.js | 2 +- .../src/utils/chain-hub-helper.js | 9 ++++-- .../orchestration/test/exos/chain-hub.test.ts | 30 +++++++++++-------- 8 files changed, 58 insertions(+), 40 deletions(-) diff --git a/packages/builders/scripts/fast-usdc/init-fast-usdc.js b/packages/builders/scripts/fast-usdc/init-fast-usdc.js index 783ce078f43..f5d38ebee82 100644 --- a/packages/builders/scripts/fast-usdc/init-fast-usdc.js +++ b/packages/builders/scripts/fast-usdc/init-fast-usdc.js @@ -20,31 +20,39 @@ import { parseArgs } from 'node:util'; * @import {ParseArgsConfig} from 'node:util' * @import {FastUSDCConfig} from '@agoric/fast-usdc/src/fast-usdc.start.js' * @import {Passable} from '@endo/marshal'; - * @import {CosmosChainInfo} from '@agoric/orchestration'; + * @import {CosmosChainInfo, Denom, DenomDetail} from '@agoric/orchestration'; */ const { keys } = Object; -const defaultAssetInfo = { - uusdc: { - baseName: 'noble', - chainName: 'noble', - baseDenom: 'uusdc', - }, - [`ibc/${denomHash({ denom: 'uusdc', channelId: fetchedChainInfo.agoric.connections['noble-1'].transferChannel.channelId })}`]: +/** @type {[Denom, DenomDetail & { brandKey?: string}][]} */ +const defaultAssetInfo = [ + [ + 'uusdc', + { + baseName: 'noble', + chainName: 'noble', + baseDenom: 'uusdc', + }, + ], + [ + `ibc/${denomHash({ denom: 'uusdc', channelId: fetchedChainInfo.agoric.connections['noble-1'].transferChannel.channelId })}`, { baseName: 'noble', chainName: 'agoric', baseDenom: 'uusdc', brandKey: 'USDC', }, - [`ibc/${denomHash({ denom: 'uusdc', channelId: fetchedChainInfo.osmosis.connections['noble-1'].transferChannel.channelId })}`]: + ], + [ + `ibc/${denomHash({ denom: 'uusdc', channelId: fetchedChainInfo.osmosis.connections['noble-1'].transferChannel.channelId })}`, { baseName: 'noble', chainName: 'osmosis', baseDenom: 'uusdc', }, -}; + ], +]; /** * @type {Record>} diff --git a/packages/fast-usdc/src/fast-usdc.contract.js b/packages/fast-usdc/src/fast-usdc.contract.js index 45bf9cc82d5..aa799e101d2 100644 --- a/packages/fast-usdc/src/fast-usdc.contract.js +++ b/packages/fast-usdc/src/fast-usdc.contract.js @@ -4,6 +4,7 @@ import { observeIteration, subscribeEach } from '@agoric/notifier'; import { CosmosChainInfoShape, DenomDetailShape, + DenomShape, OrchestrationPowersShape, registerChainsAndAssets, withOrchestration, @@ -55,7 +56,7 @@ export const meta = { feeConfig: FeeConfigShape, marshaller: M.remotable(), chainInfo: M.recordOf(M.string(), CosmosChainInfoShape), - assetInfo: M.recordOf(M.string(), DenomDetailShape), + assetInfo: M.arrayOf([DenomShape, DenomDetailShape]), }, }; harden(meta); @@ -77,7 +78,7 @@ const publishFeeConfig = async (node, marshaller, feeConfig) => { * marshaller: Marshaller; * feeConfig: FeeConfig; * chainInfo: Record; - * assetInfo: Record; + * assetInfo: [Denom, DenomDetail & { brandKey?: string}][]; * }} privateArgs * @param {Zone} zone * @param {OrchestrationTools} tools diff --git a/packages/fast-usdc/src/fast-usdc.start.js b/packages/fast-usdc/src/fast-usdc.start.js index 0dce7f375e2..237af828f30 100644 --- a/packages/fast-usdc/src/fast-usdc.start.js +++ b/packages/fast-usdc/src/fast-usdc.start.js @@ -1,5 +1,9 @@ import { deeplyFulfilledObject, makeTracer, objectMap } from '@agoric/internal'; -import { CosmosChainInfoShape, DenomDetailShape } from '@agoric/orchestration'; +import { + CosmosChainInfoShape, + DenomDetailShape, + DenomShape, +} from '@agoric/orchestration'; import { Fail } from '@endo/errors'; import { E } from '@endo/far'; import { makeMarshal } from '@endo/marshal'; @@ -36,7 +40,7 @@ const contractName = 'fastUsdc'; * feeConfig: FeeConfig; * feedPolicy: FeedPolicy & Passable; * chainInfo: Record; - * assetInfo: Record; + * assetInfo: [Denom, DenomDetail & {brandKey?: string}][]; * }} FastUSDCConfig */ /** @type {TypedPattern} */ @@ -46,7 +50,7 @@ export const FastUSDCConfigShape = M.splitRecord({ feeConfig: FeeConfigShape, feedPolicy: FeedPolicyShape, chainInfo: M.recordOf(M.string(), CosmosChainInfoShape), - assetInfo: M.recordOf(M.string(), DenomDetailShape), + assetInfo: M.arrayOf([DenomShape, DenomDetailShape]), }); /** diff --git a/packages/fast-usdc/test/supports.ts b/packages/fast-usdc/test/supports.ts index 174a28ceed9..2c9154ab227 100644 --- a/packages/fast-usdc/test/supports.ts +++ b/packages/fast-usdc/test/supports.ts @@ -200,13 +200,11 @@ export const commonSetup = async (t: ExecutionContext) => { return { agoric, osmosis, noble }; })(); - const assetInfo = harden( - Object.fromEntries([ - assetOn('uusdc', 'noble'), - [uusdcOnAgoric, agUSDCDetail], - assetOn('uusdc', 'noble', 'osmosis', fetchedChainInfo), - ]), - ); + const assetInfo: [Denom, DenomDetail & { brandKey?: string }][] = harden([ + assetOn('uusdc', 'noble'), + [uusdcOnAgoric, agUSDCDetail], + assetOn('uusdc', 'noble', 'osmosis', fetchedChainInfo), + ]); return { bootstrap: { diff --git a/packages/orchestration/src/examples/send-anywhere.contract.js b/packages/orchestration/src/examples/send-anywhere.contract.js index 3d2065de836..b277d5733ad 100644 --- a/packages/orchestration/src/examples/send-anywhere.contract.js +++ b/packages/orchestration/src/examples/send-anywhere.contract.js @@ -30,7 +30,7 @@ harden(SingleNatAmountRecord); * @param {OrchestrationPowers & { * marshaller: Marshaller; * chainInfo?: Record; - * assetInfo?: Record; + * assetInfo?: [Denom, DenomDetail & { brandKey?: string }][]; * }} privateArgs * @param {Zone} zone * @param {OrchestrationTools} tools diff --git a/packages/orchestration/src/proposals/start-send-anywhere.js b/packages/orchestration/src/proposals/start-send-anywhere.js index a50d87f3f7f..6d1c6932bc1 100644 --- a/packages/orchestration/src/proposals/start-send-anywhere.js +++ b/packages/orchestration/src/proposals/start-send-anywhere.js @@ -28,7 +28,7 @@ const trace = makeTracer('StartSA', true); * @param {{ * options: { * chainInfo: Record; - * assetInfo: Record; + * assetInfo: [Denom, DenomDetail & { brandKey?: string }][]; * }; * }} config */ diff --git a/packages/orchestration/src/utils/chain-hub-helper.js b/packages/orchestration/src/utils/chain-hub-helper.js index 306a5ecef56..829d0b58a7e 100644 --- a/packages/orchestration/src/utils/chain-hub-helper.js +++ b/packages/orchestration/src/utils/chain-hub-helper.js @@ -12,7 +12,7 @@ * @param {ChainHub} chainHub * @param {Record>} brands * @param {Record | undefined} chainInfo - * @param {Record | undefined} assetInfo + * @param {[Denom, DenomDetail & { brandKey?: string }][] | undefined} assetInfo */ export const registerChainsAndAssets = ( chainHub, @@ -43,11 +43,14 @@ export const registerChainsAndAssets = ( } console.log('chainHub: registered connections', [...registeredPairs].sort()); - console.log('chainHub: registering assets', Object.keys(assetInfo || {})); + console.log( + 'chainHub: registering assets', + assetInfo?.map(([denom, { chainName }]) => `${chainName}: ${denom}`), + ); if (!assetInfo) { return; } - for (const [denom, info] of Object.entries(assetInfo)) { + for (const [denom, info] of assetInfo) { const { brandKey, ...rest } = info; const infoWithBrand = brandKey ? { ...rest, brand: brands[brandKey] } diff --git a/packages/orchestration/test/exos/chain-hub.test.ts b/packages/orchestration/test/exos/chain-hub.test.ts index b93e4e34464..53a12302491 100644 --- a/packages/orchestration/test/exos/chain-hub.test.ts +++ b/packages/orchestration/test/exos/chain-hub.test.ts @@ -216,7 +216,10 @@ test('makeTransferRoute - to issuing chain', async t => { chainHub, {}, withChainCapabilities(knownChains), // adds pfmEnabled - harden({ [uusdcOnAgoric]: agDetail, [uusdcOnOsmosis]: osDetail }), + harden([ + [uusdcOnAgoric, agDetail], + [uusdcOnOsmosis, osDetail], + ]), ); const dest: ChainAddress = chainHub.makeChainAddress('noble1234'); @@ -257,12 +260,7 @@ test('makeTransferRoute - from issuing chain', async t => { chainHub, {}, withChainCapabilities(knownChains), // adds pfmEnabled - harden( - Object.fromEntries([ - assetOn('uist', 'agoric'), - assetOn('uosmo', 'osmosis'), - ]), - ), + harden([assetOn('uist', 'agoric'), assetOn('uosmo', 'osmosis')]), ); const dest: ChainAddress = chainHub.makeChainAddress('noble1234'); @@ -301,7 +299,7 @@ test('makeTransferRoute - through issuing chain', async t => { chainHub, {}, withChainCapabilities(knownChains), // adds pfmEnabled - harden({ [uusdcOnAgoric]: agDetail }), + harden([[uusdcOnAgoric, agDetail]]), ); const dest: ChainAddress = chainHub.makeChainAddress('osmo1234'); @@ -359,7 +357,7 @@ test('makeTransferRoute - takes forwardOpts', t => { chainHub, {}, withChainCapabilities(knownChains), // adds pfmEnabled - harden({ [uusdcOnOsmosis]: osDetail }), + harden([[uusdcOnOsmosis, osDetail]]), ); const dest: ChainAddress = chainHub.makeChainAddress('agoric1234'); @@ -464,7 +462,10 @@ test('makeTransferRoute - no connection info single hop', t => { chainHub, {}, knownChainsSansConns, // omit connections - harden({ [uusdcOnAgoric]: agDetail }), + harden([ + [uusdcOnAgoric, agDetail], + [uusdcOnOsmosis, osDetail], + ]), ); t.throws( @@ -487,7 +488,10 @@ test('makeTransferRoute - no connection info multi hop', t => { chainHub, {}, harden(chainInfo), - harden({ [uusdcOnAgoric]: agDetail, [uusdcOnOsmosis]: osDetail }), + harden([ + [uusdcOnAgoric, agDetail], + [uusdcOnOsmosis, osDetail], + ]), ); const osmoDest = chainHub.makeChainAddress('osmo1234'); @@ -522,7 +526,7 @@ test('makeTransferRoute - asset not on holding chain', t => { chainHub, {}, withChainCapabilities(knownChains), - harden({ [uusdcOnAgoric]: agDetail }), + harden([[uusdcOnAgoric, agDetail]]), ); // transfer USDC on agoric from osmosis to noble (impossible) @@ -547,7 +551,7 @@ test('makeTransferRoute - no PFM path', t => { chainHub, {}, knownChains, // intentionally omit pfmEnabled - harden({ [uusdcOnAgoric]: agDetail }), + harden([[uusdcOnAgoric, agDetail]]), ); // transfer USDC on agoric to osmosis From fc802adc06082eb0618f4a2d58d91ac380512352 Mon Sep 17 00:00:00 2001 From: 0xPatrick Date: Fri, 29 Nov 2024 14:47:23 -0500 Subject: [PATCH 2/4] feat!: `getAsset` and `getDenomInfo` require `srcChainName` param - since denoms are only unique to a chain and all chains, require `srcChainName` parameter for asset info lookups --- .../snapshots/fast-usdc.contract.test.ts.md | 14 ++--- .../snapshots/fast-usdc.contract.test.ts.snap | Bin 5637 -> 5637 bytes packages/orchestration/src/exos/chain-hub.js | 54 ++++++++++++------ .../src/exos/local-orchestration-account.js | 2 +- .../orchestration/src/exos/orchestrator.js | 6 +- .../orchestration/src/orchestration-api.ts | 1 + .../snapshots/staking-combinations.test.ts.md | 2 +- .../staking-combinations.test.ts.snap | Bin 2743 -> 2749 bytes .../orchestration/test/exos/chain-hub.test.ts | 21 +++---- .../test/facade-durability.test.ts | 26 ++++++--- packages/orchestration/test/supports.ts | 2 +- packages/orchestration/test/types.test-d.ts | 2 +- 12 files changed, 79 insertions(+), 51 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 62804cfa992..ece2a6e83cd 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 @@ -536,22 +536,22 @@ Generated by [AVA](https://avajs.dev). }, }, denom: { - 'ibc/498A0751C798A0D9A389AA3691123DADA57DAA4FE165D5C75894505B876BA6E4': { - baseDenom: 'uusdc', - baseName: 'noble', - chainName: 'osmosis', - }, - 'ibc/FE98AAD68F02F03565E9FA39A5E627946699B2B07115889ED812D8BA639576A9': { + 'agoric:ibc/FE98AAD68F02F03565E9FA39A5E627946699B2B07115889ED812D8BA639576A9': { baseDenom: 'uusdc', baseName: 'noble', brand: Object @Alleged: USDC brand {}, chainName: 'agoric', }, - uusdc: { + 'noble:uusdc': { baseDenom: 'uusdc', baseName: 'noble', chainName: 'noble', }, + 'osmosis:ibc/498A0751C798A0D9A389AA3691123DADA57DAA4FE165D5C75894505B876BA6E4': { + baseDenom: 'uusdc', + baseName: 'noble', + chainName: 'osmosis', + }, }, lookupChainInfo_kindHandle: 'Alleged: kind', lookupChainsAndConnection_kindHandle: 'Alleged: kind', 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 084687b3bab0cbb2441b180c061d6ebbda934d4e..b4f6db225e5d35f97fc59405cd299d04a75b129f 100644 GIT binary patch literal 5637 zcmV+g7W(NyRzVnkETRNrq8)vqNHo@^1(b-U#6r5ZMIANT@~R9Oe`S(Up+^D( zYBUxxfJXC+rsjf^`l)Dq%q!;S!=ijRmJj#m!%y>JaREdM;LZYgx&USsg1r!S7sBa6 z!3TNiJ}9x2KUfHl6vAtTu(Akxi$E`eJBr|35j;@@`Nhy(EQl%=MKyf57(P}E=Zm4K z1pFoNPzk*L_W#yWxTzE#D}~}R2$aFiWpHO1e5nk6QwGj*=qZO&fY2 zRl;{Gp}GpTRHak%Zbh2VvxG^|&=MR9X)VcVmFrxdC(xrItv{^BbS(*}PqEXHf}A<6 zp}3VHUCY&i1sx-rLENpKiYUXwSuLwH3G-zMQa2h?0wl1p7*17h zOo?wx;<`(==W1v}Z%m1)x%9KrV)=y0vN?(EV?+yVRQlD(US&{?L=DteOsJX^szV8d zM57`$DI8=>~3_GNc;7CDr4=GJizTg6*oN z4;gJ+&27du+@qQnqDzMBGM3uAzPbykKjijs`Utmf!*0j5~y{##UX;Q|15QJPI zLv!CfQX<+SsuI&LVfT|ZZ|cQoQ%fwh|;fSjQH2I>CquQ+O*v)ylJchO`Uo; zyFflo=E|*lKnY(&~%8)xAnAFp&GqY22fq>e6CrL{q}iOPDEjhTh$=P&k^MlN_e;Zj6{a zCWL<_mb{&_Vc%>aeaRQnmqcq}OC+L1dxerfpbZh%jUut6zT{YeA7(g2o5 zSkwsX8=w@RTUz)PzDtMIrgW zXoNQ#!82bFbc3XCcs`sIVK+%`djEX5b3XiVJ~S>6;I~Lp`xZc0faU!;6Lll+Ho0tI z5%!U?epOQw2bb|x0R}8zfE6kIdL$GOJ_?{hP4|!QL5WWSq#%9$!%lp=@ME^h#E~DiA8ix5Hf0l%pQlN%X!EIsvBcx@ct`l>XNWDNmnj1*_?!= z`F^(vQJX@p>=exTFSD{#Q@TO6t4wRigh@AP;Hu1$ z%yagv2~$0eNgAHo1HNyW0P+FM$j9UEv6ZX zxHmdYz3VsWC=Mhm7V)a0si88NlJ)4PlJ&$No7#(p$EpJvw(I%3B8nDOjA~N06^}%q zUum(_E;eNeemxS2>o;09n_4P^>aZRS#S%gj?e8%&)zgW1+GzRO;tYnz3x%fdizDi= zGCC%zd5LLrI~9rz1S86+l&%b2E{&(qy#q=l)xqji2X6@UbsJf4f)~$VP2#JEltI-< z^~Q2V1DE%I{D>-L>PJkbrjEl5LBYbV);7cKeQKmv)q-l|Qc~WKKd@7cj)Y^Enn_6l zCa83Y*o=;C=owE(v+GfbrSP#O@Qo$#`Vy#L3f`qcNLMaJfP0t1;id5IrSO0VuN2{h z&n$)Km%>|1Vc9b1TqaPeL`qTrG8kG0pI8QuE`yhr0m}v2Y>BpLIV@Wa`?;M@0*ThU685iz zJ66JHR>JpI!fPUJp+u`%1$C=n(<)F`!I@R?*;NAVDv9>gD)`Ew?T&uhHP-VO`xrlXrHse=WXzk4J>w8VTTU8 zK)Xg>ICdDa!&y80y&Yb*L!LvRT`$qDcEAb;9CW~_1MYReV^QlxE_Xf<<$*2_^mzo@PI=*Y;EV?z_rQxDDD}cpuRz-^(KdOZ z+Y7gP;j9ZJ$Id_CbXYT6}QS2k-U4r+otLphSDp2T%LJvJMumgRXVZw@#oP zkv8&ouY)t|;Nf-fFYDk9k>Zz*iiY*Da6NRahwyp<9+co8Sq~py4_{jk7eu%^Vd-O_ z#lJ*>>cK3-$F@?z{n?3Rf&RAXsMv4kfv$tW@tPcz#p z@X}+6SURK=$xS#1!@SE0a(jaocPPC)aj6NQQm zrl(2Km@*iD2Br_HS~O!Io6)e0^()b?;#68csae?$h*2V&L~W@lqn=oC;s)4*6OTus znArrvAytbV4W$%TSv}1$BgBtC%xiWhY9%JAvy41OjSMT1*r*^bZ(Po-@tYIPUa@=_ zjo*q%R3XRG)<2juRK>1ovDwwQ9bC5{`TvqY<^9;`F2;L;`;unS#Nh`v0lGCb?b#)GBIs_o4f7iX_Ljc zU)?g@mM4xTf6J6C&tlBaOxu;ug+!Hg#-TKF#F-q%n z?~%@g>7UHqbo2i1$RZ|qYT9kLB{rF|wmx<}of*kn4}r#9Qg{5ON`(P_7REFk#8)NHqBF=Nh8yX|8+(}Imt%lDj< z+rDYzbkFw0Lj8-}P0umQ{`}M}7lTNbH(~rQb2q-qyhGZZS&aPrj_J4GZ8t<8=$NAY z_AFNHyQbZKyKU@*`omXi`ZuWrb+A<>vpAnC8`>) z4W*=xUFS%DHzp2?UxiMg#_y1Q%%ro>xZEaCO9lo?3p5HFe7fG6aza8>=z||u_ua-20dO+`idwSrx z9w^=cOLsu`4miF;pf8p*eP{>#fNw^H+*n6Jh2;ox*JOOz|uVey;;)Ky$5>tz&l0gT1nFv_rRlj;NSK@(_ZM^ z3z5C>@xAcHy#n1TX*$0bUfK(F`vj<6($u*Rw(Wz_een5x@Uwl8zaLiahfVthxgVC5m$aR{_S0^KKRy6X_!a|oUhq3b101&5*RFt`rG&|&!GVfgA{xNsQq zj|lV)lBShMzisc-W7ny0`Nuvs)OJO!mgk|@02v%7=&Ac z@URHoENOZ*2(Ja9QH7l|!SfKBfG#v>;FbsEz(EXC8C&Tb`7=9N9>ktHn;I<+7%n*EQ zNT45(H2q=--W-C(ngBf{Y1*T~Ar0QE!Phi+Q-fL^d^+sW1^Qu0)6F`(Lx+b&=ut`2 z1s(oFhlXM38HRTZ!+pc>?P0huEYOclcws1z`CXk%&_7GNIwQR5YQ6+hX2kmC7E8qm z<97h1K06biGsNbsqZ*My2Q%2hX(wf4lb z);5t=o}@XgBC*y??9n4J(`}e!k}4)y##QfRLKA; zTw+l&OOH6jV$Nmh5vi7E^=0Lg^uk^4JC``!v)s8H_8H-o8tIxu&I`qx^FooTmsu_p z*NoV@M3>mJTPNlN-WGobo8#ssYRPh)*qq|7HzS@MY-{vC!p}F3V+cnm8OeXYe^z zRZ^RtSGopzS-M4xx^gdcWnFt5--2>GPR1A5j9e%lYj(L}+Cs5Av&~MXS`qfRW<+)( zR=PZ5ud}Fh*|W`0#y2l9l*n?WcxS{)5#I)s;{VBgVz=dd%;#8r9FL--CO5)tO@RiRN9FUJ)|+?8@|)Qh~hqxsYWdKO>$n z%C8^h=#HFsEl+Ouhh|nic*^zpoS&CC?3ehwWJcWPl7B1EA8C1v9|PV?tULaHUmqow zyn`n|IU(H5tPrnu=Dp(tyjO(PNWYv*e3vTmC##a53P$|lU>3N-k@)*rYFP2B;nNF} ff1j{v?C&}@DSa_D(iH!#&9VOlRUJ(6{BZyPca#7q literal 5637 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 diff --git a/packages/orchestration/src/exos/chain-hub.js b/packages/orchestration/src/exos/chain-hub.js index f74d9eda964..6a300fade5a 100644 --- a/packages/orchestration/src/exos/chain-hub.js +++ b/packages/orchestration/src/exos/chain-hub.js @@ -205,7 +205,9 @@ const ChainHubI = M.interface('ChainHub', { getConnectionInfo: M.call(ChainIdArgShape, ChainIdArgShape).returns(VowShape), getChainsAndConnection: M.call(M.string(), M.string()).returns(VowShape), registerAsset: M.call(M.string(), DenomDetailShape).returns(), - getAsset: M.call(M.string()).returns(M.or(DenomDetailShape, M.undefined())), + getAsset: M.call(M.string(), 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()) @@ -256,6 +258,12 @@ export const makeChainHub = (zone, agoricNames, vowTools) => { valueShape: M.string(), }); + /** + * @param {Denom} denom - from perspective of the src/holding chain + * @param {DenomDetail['chainName']} srcChainName + */ + const makeDenomKey = (denom, srcChainName) => `${srcChainName}:${denom}`; + const lookupChainInfo = vowTools.retryable( zone, 'lookupChainInfo', @@ -440,8 +448,14 @@ export const makeChainHub = (zone, agoricNames, vowTools) => { Fail`must register chain ${q(chainName)} first`; chainInfos.has(baseName) || Fail`must register chain ${q(baseName)} first`; - denomDetails.init(denom, detail); + + const denomKey = makeDenomKey(denom, detail.chainName); + denomDetails.has(denomKey) && + Fail`already registered ${q(denom)} on ${q(chainName)}`; + denomDetails.init(denomKey, detail); if (detail.brand) { + chainName === 'agoric' || + Fail`brands only registerable for agoric-held assets`; brandDenoms.init(detail.brand, denom); } }, @@ -449,11 +463,13 @@ export const makeChainHub = (zone, agoricNames, vowTools) => { * Retrieve holding, issuing chain names etc. for a denom. * * @param {Denom} denom + * @param {string} srcChainName - the chainName the denom is held on * @returns {DenomDetail | undefined} */ - getAsset(denom) { - if (denomDetails.has(denom)) { - return denomDetails.get(denom); + getAsset(denom, srcChainName) { + const denomKey = makeDenomKey(denom, srcChainName); + if (denomDetails.has(denomKey)) { + return denomDetails.get(denomKey); } return undefined; }, @@ -491,26 +507,31 @@ export const makeChainHub = (zone, agoricNames, vowTools) => { * Determine the transfer route for a destination and amount given the * current holding chain. * + * Does not account for routes with more than 1 intermediary hop - that is, + * it can't unwrap denoms that were incorrectly routed. + * * XXX consider accepting AmountArg #10449 * * @param {ChainAddress} destination * @param {DenomAmount} denomAmount - * @param {string} holdingChainName + * @param {string} srcChainName * @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)}`; + makeTransferRoute(destination, denomAmount, srcChainName, forwardOpts) { + chainInfos.has(srcChainName) || + Fail`chain info not found for holding chain: ${q(srcChainName)}`; - const denomDetail = chainHub.getAsset(denomAmount.denom); + const denomDetail = chainHub.getAsset(denomAmount.denom, srcChainName); denomDetail || - Fail`no denom detail for: ${q(denomAmount.denom)}. ensure it is registered in chainHub.`; + Fail`no denom detail for: ${q(denomAmount.denom)} on ${q(srcChainName)}. 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 assets are registered with holdingChainName + chainName === srcChainName || + Fail`cannot transfer asset ${q(denomAmount.denom)}. held on ${q(chainName)} not ${q(srcChainName)}.`; // currently unreachable since we can't register an asset before a chain chainInfos.has(baseName) || @@ -518,13 +539,10 @@ export const makeChainHub = (zone, agoricNames, vowTools) => { const { chainId: baseChainId, pfmEnabled } = chainInfos.get(baseName); - const holdingChainId = chainInfos.get(holdingChainName).chainId; + const holdingChainId = chainInfos.get(srcChainName).chainId; // asset is transferring to or from the issuing chain, return direct route - if ( - baseChainId === destination.chainId || - baseName === holdingChainName - ) { + if (baseChainId === destination.chainId || baseName === srcChainName) { // TODO use getConnectionInfo once its sync const connKey = connectionKey(holdingChainId, destination.chainId); connectionInfos.has(connKey) || diff --git a/packages/orchestration/src/exos/local-orchestration-account.js b/packages/orchestration/src/exos/local-orchestration-account.js index bc5e1f2a48a..7ab823a0719 100644 --- a/packages/orchestration/src/exos/local-orchestration-account.js +++ b/packages/orchestration/src/exos/local-orchestration-account.js @@ -511,7 +511,7 @@ export const prepareLocalOrchestrationAccountKit = ( return asVow(() => { const [brand, denom] = typeof denomArg === 'string' - ? [chainHub.getAsset(denomArg)?.brand, denomArg] + ? [chainHub.getAsset(denomArg, 'agoric')?.brand, denomArg] : [denomArg, chainHub.getDenom(denomArg)]; if (!denom) { diff --git a/packages/orchestration/src/exos/orchestrator.js b/packages/orchestration/src/exos/orchestrator.js index 70e02ee60e7..0d5dae350b8 100644 --- a/packages/orchestration/src/exos/orchestrator.js +++ b/packages/orchestration/src/exos/orchestrator.js @@ -34,7 +34,7 @@ const trace = makeTracer('Orchestrator'); /** @see {Orchestrator} */ export const OrchestratorI = M.interface('Orchestrator', { getChain: M.call(M.string()).returns(Vow$(ChainInfoShape)), - getDenomInfo: M.call(DenomShape).returns(DenomInfoShape), + getDenomInfo: M.call(DenomShape, M.string()).returns(DenomInfoShape), asAmount: M.call(DenomAmountShape).returns(AmountShape), }); @@ -138,8 +138,8 @@ const prepareOrchestratorKit = ( }); }, /** @type {HostOf} */ - getDenomInfo(denom) { - const denomDetail = chainHub.getAsset(denom); + getDenomInfo(denom, holdingChainName) { + const denomDetail = chainHub.getAsset(denom, holdingChainName); if (!denomDetail) throw Fail`No denom detail for ${q(denom)}`; const { chainName, baseName, baseDenom, brand } = denomDetail; chainByName.has(chainName) || diff --git a/packages/orchestration/src/orchestration-api.ts b/packages/orchestration/src/orchestration-api.ts index 50830862c05..2294f376dce 100644 --- a/packages/orchestration/src/orchestration-api.ts +++ b/packages/orchestration/src/orchestration-api.ts @@ -146,6 +146,7 @@ export interface Orchestrator { IssuingChain extends keyof KnownChains, >( denom: Denom, + srcChainName: HoldingChain, ) => DenomInfo; /** diff --git a/packages/orchestration/test/examples/snapshots/staking-combinations.test.ts.md b/packages/orchestration/test/examples/snapshots/staking-combinations.test.ts.md index bbf2ad9192b..378cdaddaf7 100644 --- a/packages/orchestration/test/examples/snapshots/staking-combinations.test.ts.md +++ b/packages/orchestration/test/examples/snapshots/staking-combinations.test.ts.md @@ -98,7 +98,7 @@ Generated by [AVA](https://avajs.dev). }, }, denom: { - ubld: { + 'agoric:ubld': { baseDenom: 'ubld', baseName: 'agoric', brand: Object @Alleged: BLD brand {}, diff --git a/packages/orchestration/test/examples/snapshots/staking-combinations.test.ts.snap b/packages/orchestration/test/examples/snapshots/staking-combinations.test.ts.snap index 01a323d924f17902e88e58d2a0c44d1e0f98fc89..89494e9934d40e5f508d23d3c19b809d99d905d1 100644 GIT binary patch literal 2749 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 diff --git a/packages/orchestration/test/exos/chain-hub.test.ts b/packages/orchestration/test/exos/chain-hub.test.ts index 53a12302491..1a8efc8e762 100644 --- a/packages/orchestration/test/exos/chain-hub.test.ts +++ b/packages/orchestration/test/exos/chain-hub.test.ts @@ -86,28 +86,28 @@ test('denom info support via getAsset and getDenom', async t => { const denom = 'utok1'; const info1: CosmosChainInfo = { bech32Prefix: 'chain', - chainId: 'chain1', + chainId: 'agoric', stakingTokens: [{ denom }], }; const tok1 = withAmountUtils(makeIssuerKit('Tok1')); - chainHub.registerChain('chain1', info1); + chainHub.registerChain('agoric', info1); const info = { - chainName: 'chain1', - baseName: 'chain1', + chainName: 'agoric', + baseName: 'agoric', baseDenom: denom, brand: tok1.brand, }; chainHub.registerAsset('utok1', info); t.deepEqual( - chainHub.getAsset('utok1'), + chainHub.getAsset('utok1', 'agoric'), info, 'getAsset(denom) returns denom info', ); t.is( - chainHub.getAsset('utok404'), + chainHub.getAsset('utok404', 'agoric'), undefined, 'getAsset returns undefined when denom not registered', ); @@ -145,7 +145,7 @@ test('toward asset info in agoricNames (#9572)', async t => { registerAssets(chainHub, 'cosmoshub', details); { - const actual = chainHub.getAsset('uatom'); + const actual = chainHub.getAsset('uatom', 'cosmoshub'); t.deepEqual(actual, { chainName: 'cosmoshub', baseName: 'cosmoshub', @@ -156,6 +156,7 @@ test('toward asset info in agoricNames (#9572)', async t => { { const actual = chainHub.getAsset( 'ibc/F04D72CF9B5D9C849BB278B691CDFA2241813327430EC9CDC83F8F4CA4CDC2B0', + 'cosmoshub', ); t.deepEqual(actual, { chainName: 'cosmoshub', @@ -432,7 +433,7 @@ test('makeTransferRoute - no asset info', t => { ), { message: - 'no denom detail for: "uist". ensure it is registered in chainHub.', + 'no denom detail for: "uist" on "agoric". ensure it is registered in chainHub.', }, ); @@ -445,7 +446,7 @@ test('makeTransferRoute - no asset info', t => { ), { message: - 'no denom detail for: "ibc/FE98AAD68F02F03565E9FA39A5E627946699B2B07115889ED812D8BA639576A9". ensure it is registered in chainHub.', + 'no denom detail for: "ibc/FE98AAD68F02F03565E9FA39A5E627946699B2B07115889ED812D8BA639576A9" on "agoric". ensure it is registered in chainHub.', }, ); }); @@ -539,7 +540,7 @@ test('makeTransferRoute - asset not on holding chain', t => { ), { message: - 'cannot transfer asset "ibc/FE98AAD68F02F03565E9FA39A5E627946699B2B07115889ED812D8BA639576A9". held on "agoric" not "osmosis".', + 'no denom detail for: "ibc/FE98AAD68F02F03565E9FA39A5E627946699B2B07115889ED812D8BA639576A9" on "osmosis". ensure it is registered in chainHub.', }, ); }); diff --git a/packages/orchestration/test/facade-durability.test.ts b/packages/orchestration/test/facade-durability.test.ts index e6c3a4fe9ac..dff35064c26 100644 --- a/packages/orchestration/test/facade-durability.test.ts +++ b/packages/orchestration/test/facade-durability.test.ts @@ -184,7 +184,7 @@ test('asset / denom info', async t => { const { chainHub, orchestrate } = orchKit; chainHub.registerChain('agoric', fetchedChainInfo.agoric); - chainHub.registerChain(mockChainInfo.chainId, mockChainInfo); + chainHub.registerChain('mock', mockChainInfo); chainHub.registerConnection( 'agoric-3', mockChainInfo.chainId, @@ -192,8 +192,8 @@ test('asset / denom info', async t => { ); chainHub.registerAsset('utoken1', { - chainName: mockChainInfo.chainId, - baseName: mockChainInfo.chainId, + chainName: 'mock', + baseName: 'mock', baseDenom: 'utoken1', }); @@ -203,7 +203,7 @@ test('asset / denom info', async t => { t.log(`utoken1 over ${channelId}: ${agDenom}`); chainHub.registerAsset(agDenom, { chainName: 'agoric', - baseName: mockChainInfo.chainId, + baseName: 'mock', baseDenom: 'utoken1', brand, }); @@ -213,10 +213,14 @@ test('asset / denom info', async t => { { brand }, // eslint-disable-next-line no-shadow async (orc, { brand }) => { - const c1 = await orc.getChain(mockChainInfo.chainId); + const c1 = await orc.getChain('mock'); { - const actual = orc.getDenomInfo('utoken1'); + const actual = orc.getDenomInfo( + 'utoken1', + // @ts-expect-error 'mock' not a KnownChain + 'mock', + ); console.log('actual', actual); const info = await actual.chain.getChainInfo(); t.deepEqual(info, mockChainInfo); @@ -230,12 +234,12 @@ test('asset / denom info', async t => { } const agP = orc.getChain('agoric'); - t.throws(() => orc.getDenomInfo(agDenom), { + t.throws(() => orc.getDenomInfo(agDenom, 'agoric'), { message: /^wait until getChain\("agoric"\) completes/, }); const ag = await agP; { - const actual = orc.getDenomInfo(agDenom); + const actual = orc.getDenomInfo(agDenom, 'agoric'); t.deepEqual(actual, { chain: ag, @@ -258,7 +262,11 @@ test('asset / denom info', async t => { }); const missingGetChain = orchestrate('missing getChain', {}, async orc => { - const actual = orc.getDenomInfo('utoken2'); + const actual = orc.getDenomInfo( + 'utoken2', + // @ts-expect-error 'mock' not a KnownChain + 'anotherChain', + ); }); await t.throwsAsync(vt.when(missingGetChain()), { diff --git a/packages/orchestration/test/supports.ts b/packages/orchestration/test/supports.ts index dbf539c0919..6486a7ffbaa 100644 --- a/packages/orchestration/test/supports.ts +++ b/packages/orchestration/test/supports.ts @@ -174,7 +174,7 @@ export const commonSetup = async (t: ExecutionContext) => { * ChainHub. Use `ChainHubAdmin` instead. */ const registerAgoricBld = () => { - if (!chainHub.getAsset('ubld')) { + if (!chainHub.getAsset('ubld', 'agoric')) { chainHub.registerChain('agoric', fetchedChainInfo.agoric); chainHub.registerAsset('ubld', { chainName: 'agoric', diff --git a/packages/orchestration/test/types.test-d.ts b/packages/orchestration/test/types.test-d.ts index a889ca536e6..0b13a42fc19 100644 --- a/packages/orchestration/test/types.test-d.ts +++ b/packages/orchestration/test/types.test-d.ts @@ -110,7 +110,7 @@ expectNotType(chainAddr); expectNotType<() => Promise>(vowFn); const getDenomInfo: HostOf = null as any; - const chainHostOf = getDenomInfo('uatom').chain; + const chainHostOf = getDenomInfo('uatom', 'cosmoshub').chain; expectType>(chainHostOf.getChainInfo()); } From 50b36bad0ae62cbd9205532ebf5bb637bc06beed Mon Sep 17 00:00:00 2001 From: 0xPatrick Date: Fri, 29 Nov 2024 16:49:53 -0500 Subject: [PATCH 3/4] feat: multichain-testing `makeAssetInfo` helper --- multichain-testing/test/send-anywhere.test.ts | 35 +--- multichain-testing/test/support.ts | 7 + .../test/tools/asset-info.test.ts | 167 ++++++++++++++++++ multichain-testing/tools/asset-info.ts | 76 ++++++++ 4 files changed, 254 insertions(+), 31 deletions(-) create mode 100644 multichain-testing/test/tools/asset-info.test.ts create mode 100644 multichain-testing/tools/asset-info.ts diff --git a/multichain-testing/test/send-anywhere.test.ts b/multichain-testing/test/send-anywhere.test.ts index 376853600f0..ba74b23338a 100644 --- a/multichain-testing/test/send-anywhere.test.ts +++ b/multichain-testing/test/send-anywhere.test.ts @@ -10,8 +10,6 @@ import { createWallet } from '../tools/wallet.js'; import { AmountMath } from '@agoric/ertp'; import { makeQueryClient } from '../tools/query.js'; import type { Amount } from '@agoric/ertp/src/types.js'; -import chainInfo from '../starship-chain-info.js'; -import { denomHash, withChainCapabilities } from '@agoric/orchestration'; const test = anyTest as TestFn; @@ -22,39 +20,14 @@ const contractBuilder = '../packages/builders/scripts/testing/init-send-anywhere.js'; test.before(async t => { - const { deleteTestKeys, setupTestKeys, ...rest } = await commonSetup(t); + const { setupTestKeys, ...common } = await commonSetup(t); + const { assetInfo, chainInfo, deleteTestKeys, startContract } = common; deleteTestKeys(accounts).catch(); const wallets = await setupTestKeys(accounts); - t.context = { ...rest, wallets, deleteTestKeys }; - const { startContract } = rest; - - const assetInfo = { - uosmo: { - baseName: 'osmosis', - chainName: 'osmosis', - baseDenom: 'uosmo', - }, - [`ibc/${denomHash({ denom: 'uosmo', channelId: chainInfo.agoric.connections['osmosislocal'].transferChannel.channelId })}`]: - { - baseName: 'osmosis', - chainName: 'agoric', - baseDenom: 'uosmo', - }, - uatom: { - baseName: 'cosmoshub', - chainName: 'cosmoshub', - baseDenom: 'uatom', - }, - [`ibc/${denomHash({ denom: 'uatom', channelId: chainInfo.agoric.connections['gaialocal'].transferChannel.channelId })}`]: - { - baseName: 'cosmoshub', - chainName: 'agoric', - baseDenom: 'uatom', - }, - }; + t.context = { ...common, wallets }; await startContract(contractName, contractBuilder, { - chainInfo: JSON.stringify(withChainCapabilities(chainInfo)), + chainInfo: JSON.stringify(chainInfo), assetInfo: JSON.stringify(assetInfo), }); }); diff --git a/multichain-testing/test/support.ts b/multichain-testing/test/support.ts index f98b5a13f08..aef2bfab173 100644 --- a/multichain-testing/test/support.ts +++ b/multichain-testing/test/support.ts @@ -3,6 +3,7 @@ import { dirname, join } from 'path'; import { execa } from 'execa'; import fse from 'fs-extra'; import childProcess from 'node:child_process'; +import { withChainCapabilities } from '@agoric/orchestration'; import { makeAgdTools } from '../tools/agd-tools.js'; import { type E2ETools } from '../tools/e2e-tools.js'; import { @@ -15,6 +16,8 @@ import { makeRetryUntilCondition } from '../tools/sleep.js'; import { makeDeployBuilder } from '../tools/deploy.js'; import { makeHermes } from '../tools/hermes-tools.js'; import { makeNobleTools } from '../tools/noble-tools.js'; +import { makeAssetInfo } from '../tools/asset-info.js'; +import starshipChainInfo from '../starship-chain-info.js'; export const FAUCET_POUR = 10_000n * 1_000_000n; @@ -78,6 +81,8 @@ export const commonSetup = async (t: ExecutionContext) => { }); const hermes = makeHermes(childProcess); const nobleTools = makeNobleTools(childProcess); + const assetInfo = makeAssetInfo(starshipChainInfo); + const chainInfo = withChainCapabilities(starshipChainInfo); /** * Starts a contract if instance not found. Takes care of installing @@ -116,6 +121,8 @@ export const commonSetup = async (t: ExecutionContext) => { hermes, nobleTools, startContract, + assetInfo, + chainInfo, }; }; diff --git a/multichain-testing/test/tools/asset-info.test.ts b/multichain-testing/test/tools/asset-info.test.ts new file mode 100644 index 00000000000..f12748de7fd --- /dev/null +++ b/multichain-testing/test/tools/asset-info.test.ts @@ -0,0 +1,167 @@ +import test from '@endo/ses-ava/prepare-endo.js'; +import type { Denom, DenomDetail } from '@agoric/orchestration'; +import { makeAssetInfo } from '../../tools/asset-info.js'; + +const minChainInfo = { + agoric: { + chainId: 'agoriclocal', + connections: { + gaialocal: { + transferChannel: { + channelId: 'channel-1', + }, + }, + osmosislocal: { + transferChannel: { + channelId: 'channel-0', + }, + }, + }, + }, + cosmoshub: { + chainId: 'gaialocal', + connections: { + agoriclocal: { + transferChannel: { + channelId: 'channel-1', + }, + }, + osmosislocal: { + transferChannel: { + channelId: 'channel-0', + }, + }, + }, + }, + osmosis: { + chainId: 'osmosislocal', + connections: { + agoriclocal: { + transferChannel: { + channelId: 'channel-1', + }, + }, + gaialocal: { + transferChannel: { + channelId: 'channel-0', + }, + }, + }, + }, +}; + +const minTokenMap = { + agoric: ['ubld', 'uist'], + cosmoshub: ['uatom'], + osmosis: ['uosmo'], +}; + +test('makeAssetInfo', async t => { + const byDenom = (assetInfo: [Denom, DenomDetail][]) => + assetInfo.sort(([a], [b]) => a.localeCompare(b) * -1); + + const assetInfo = makeAssetInfo( + /** @ts-expect-error minified mock */ + minChainInfo, + minTokenMap, + ); + + t.deepEqual(byDenom([...assetInfo]), [ + [ + 'uosmo', + { + baseDenom: 'uosmo', + baseName: 'osmosis', + chainName: 'osmosis', + }, + ], + [ + 'uist', + { + baseDenom: 'uist', + baseName: 'agoric', + chainName: 'agoric', + }, + ], + [ + 'ubld', + { + baseDenom: 'ubld', + baseName: 'agoric', + chainName: 'agoric', + }, + ], + [ + 'uatom', + { + baseDenom: 'uatom', + baseName: 'cosmoshub', + chainName: 'cosmoshub', + }, + ], + [ + 'ibc/ED07A3391A112B175915CD8FAF43A2DA8E4790EDE12566649D0C2F97716B8518', + { + baseDenom: 'uosmo', + baseName: 'osmosis', + chainName: 'agoric', + }, + ], + [ + 'ibc/ED07A3391A112B175915CD8FAF43A2DA8E4790EDE12566649D0C2F97716B8518', + { + baseDenom: 'uosmo', + baseName: 'osmosis', + chainName: 'cosmoshub', + }, + ], + [ + 'ibc/E7827844CB818EE9C4DB2C159F1543FF62B26213B44CE8029D5CEFE52F0EE596', + { + baseDenom: 'ubld', + baseName: 'agoric', + chainName: 'cosmoshub', + }, + ], + [ + 'ibc/E7827844CB818EE9C4DB2C159F1543FF62B26213B44CE8029D5CEFE52F0EE596', + { + baseDenom: 'ubld', + baseName: 'agoric', + chainName: 'osmosis', + }, + ], + [ + 'ibc/C4CFF46FD6DE35CA4CF4CE031E643C8FDC9BA4B99AE598E9B0ED98FE3A2319F9', + { + baseDenom: 'uatom', + baseName: 'cosmoshub', + chainName: 'agoric', + }, + ], + [ + 'ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2', + { + baseDenom: 'uatom', + baseName: 'cosmoshub', + chainName: 'osmosis', + }, + ], + [ + 'ibc/16CD81E12F05F5397CA2D580B4BA786A12A8F48B6FB3823D82EBE95D80B5287B', + { + baseDenom: 'uist', + baseName: 'agoric', + chainName: 'cosmoshub', + }, + ], + [ + 'ibc/16CD81E12F05F5397CA2D580B4BA786A12A8F48B6FB3823D82EBE95D80B5287B', + { + baseDenom: 'uist', + baseName: 'agoric', + chainName: 'osmosis', + }, + ], + ]); +}); diff --git a/multichain-testing/tools/asset-info.ts b/multichain-testing/tools/asset-info.ts new file mode 100644 index 00000000000..34c7d536b8c --- /dev/null +++ b/multichain-testing/tools/asset-info.ts @@ -0,0 +1,76 @@ +import { + denomHash, + type CosmosChainInfo, + type Denom, + type DenomDetail, +} from '@agoric/orchestration'; +import type { IBCChannelID } from '@agoric/vats'; + +/** make asset info for current env */ +export const makeAssetInfo = ( + chainInfo: Record, + tokenMap: Record = { + agoric: ['ubld', 'uist'], + cosmoshub: ['uatom'], + noble: ['uusdc'], + osmosis: ['uosmo', 'uion'], + }, +): [Denom, DenomDetail][] => { + const getChannelId = ( + issuingChainId: string, + holdingChainName: string, + ): IBCChannelID | undefined => + chainInfo[holdingChainName]?.connections?.[issuingChainId]?.transferChannel + .channelId; + + const toDenomHash = ( + denom: Denom, + issuingChainId: string, + holdingChainName: string, + ): Denom => { + const channelId = getChannelId(issuingChainId, holdingChainName); + if (!channelId) { + throw new Error( + `No channel found for ${issuingChainId} -> ${holdingChainName}`, + ); + } + return `ibc/${denomHash({ denom, channelId })}`; + }; + + // only include chains present in `chainInfo` + const tokens = Object.entries(tokenMap) + .filter(([chain]) => chain in chainInfo) + .flatMap(([chain, denoms]) => denoms.map(denom => ({ denom, chain }))); + + const assetInfo: [Denom, DenomDetail][] = []; + for (const { denom, chain } of tokens) { + const baseDetails = { + baseName: chain, + baseDenom: denom, + }; + + // Add native token entry + assetInfo.push([ + denom, + { + ...baseDetails, + chainName: chain, + }, + ]); + + // Add IBC entries for non-issuing chains + const issuingChainId = chainInfo[chain].chainId; + for (const holdingChain of Object.keys(chainInfo)) { + if (holdingChain === chain) continue; + assetInfo.push([ + toDenomHash(denom, issuingChainId, holdingChain), + { + ...baseDetails, + chainName: holdingChain, + }, + ]); + } + } + + return harden(assetInfo); +}; From 5912769ceca9bf97cc0fbf9f83c8e8290f1a4a61 Mon Sep 17 00:00:00 2001 From: 0xPatrick Date: Mon, 2 Dec 2024 16:09:56 -0500 Subject: [PATCH 4/4] chore(z:acceptance): skip "user can open a vault under debt limit" - filed #10599 after observing multiple flakes from unrelated changes --- a3p-integration/proposals/z:acceptance/vaults.test.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/a3p-integration/proposals/z:acceptance/vaults.test.js b/a3p-integration/proposals/z:acceptance/vaults.test.js index 7f3b818549d..bb8d9a40cb5 100644 --- a/a3p-integration/proposals/z:acceptance/vaults.test.js +++ b/a3p-integration/proposals/z:acceptance/vaults.test.js @@ -226,7 +226,8 @@ test.serial('user cannot open a vault above debt limit', async t => { ); }); -test.serial('user can open a vault under debt limit', async t => { +// TODO #10599. marked as `skip` since several flakes were observed +test.skip('user can open a vault under debt limit', async t => { const istBalanceBefore = await getISTBalance(GOV1ADDR); const activeVaultsBefore = await listVaults(GOV1ADDR, walletUtils);