From 4b880d161c9588adf410f9b39d294b233ed93bd6 Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Tue, 27 Aug 2024 12:29:16 -0700 Subject: [PATCH 01/14] feat: BrandInfoShape --- .../orchestration/src/exos/orchestrator.js | 1 - .../orchestration/src/orchestration-api.ts | 25 +++++++++++-------- packages/orchestration/src/typeGuards.js | 12 ++++++--- packages/orchestration/test/facade.test.ts | 1 + 4 files changed, 25 insertions(+), 14 deletions(-) diff --git a/packages/orchestration/src/exos/orchestrator.js b/packages/orchestration/src/exos/orchestrator.js index 6aad87ef1ea..bc525e992a5 100644 --- a/packages/orchestration/src/exos/orchestrator.js +++ b/packages/orchestration/src/exos/orchestrator.js @@ -25,7 +25,6 @@ import { * @import {Remote} from '@agoric/internal'; * @import {PickFacet} from '@agoric/swingset-liveslots'; * @import {CosmosInterchainService} from './cosmos-interchain-service.js'; - * @import {MakeLocalOrchestrationAccountKit} from './local-orchestration-account.js'; * @import {MakeLocalChainFacade} from './local-chain-facade.js'; * @import {MakeRemoteChainFacade} from './remote-chain-facade.js'; * @import {Chain, ChainInfo, IBCConnectionInfo, Orchestrator} from '../types.js'; diff --git a/packages/orchestration/src/orchestration-api.ts b/packages/orchestration/src/orchestration-api.ts index 6fa1be523fe..92c36edf3e2 100644 --- a/packages/orchestration/src/orchestration-api.ts +++ b/packages/orchestration/src/orchestration-api.ts @@ -101,6 +101,20 @@ export interface Chain { // TODO provide a way to get the local denom/brand/whatever for this chain } +export interface BrandInfo< + HoldingChain extends keyof KnownChains, + IssuingChain extends keyof KnownChains, +> { + /** The well-known Brand on Agoric for the direct asset */ + brand?: Brand; + /** The Chain at which the argument `denom` exists (where the asset is currently held) */ + chain: Chain; + /** The Chain that is the issuer of the underlying asset */ + base: Chain; + /** the Denom for the underlying asset on its issuer chain */ + baseDenom: Denom; +} + /** * Provided in the callback to `orchestrate()`. */ @@ -124,16 +138,7 @@ export interface Orchestrator { IssuingChain extends keyof KnownChains, >( denom: Denom, - ) => { - /** The well-known Brand on Agoric for the direct asset */ - brand?: Brand; - /** The Chain at which the argument `denom` exists (where the asset is currently held) */ - chain: Chain; - /** The Chain that is the issuer of the underlying asset */ - base: Chain; - /** the Denom for the underlying asset on its issuer chain */ - baseDenom: Denom; - }; + ) => BrandInfo; // TODO preload the mapping so this can be synchronous /** * Convert an amount described in native data to a local, structured Amount. diff --git a/packages/orchestration/src/typeGuards.js b/packages/orchestration/src/typeGuards.js index fad82ee03e9..2d27c39cd95 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, ChainInfo, CosmosChainInfo, DenomAmount} from './types.js'; + * @import {ChainAddress, CosmosAssetInfo, Chain, ChainInfo, CosmosChainInfo, DenomAmount, DenomDetail, BrandInfo} from './types.js'; * @import {Delegation} from '@agoric/cosmic-proto/cosmos/staking/v1beta1/staking.js'; * @import {TxBody} from '@agoric/cosmic-proto/cosmos/tx/v1beta1/tx.js'; * @import {TypedJson} from '@agoric/cosmic-proto'; @@ -111,8 +111,14 @@ export const ChainInfoShape = M.splitRecord({ }); export const LocalChainAccountShape = M.remotable('LocalChainAccount'); export const DenomShape = M.string(); -// TODO define for #9211 -export const BrandInfoShape = M.any(); + +/** @type {TypedPattern>} */ +export const BrandInfoShape = { + chain: M.remotable('Chain'), + base: M.remotable('Chain'), + brand: M.or(M.remotable('Brand'), M.undefined()), + baseDenom: M.string(), +}; /** @type {TypedPattern} */ export const DenomAmountShape = { denom: DenomShape, value: M.bigint() }; diff --git a/packages/orchestration/test/facade.test.ts b/packages/orchestration/test/facade.test.ts index 5655d80895f..63eaf8fc681 100644 --- a/packages/orchestration/test/facade.test.ts +++ b/packages/orchestration/test/facade.test.ts @@ -186,6 +186,7 @@ test.serial('asset / denom info', async t => { { const actual = orc.getBrandInfo('utoken1'); + console.log('actual', actual); const info = await actual.chain.getChainInfo(); t.deepEqual(info, mockChainInfo); From 27992e65dfb930fbcb6ae20c04bc4e5a5f489707 Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Tue, 27 Aug 2024 15:59:24 -0700 Subject: [PATCH 02/14] chore(types): nat Brand --- packages/orchestration/src/exos/chain-hub.js | 2 +- packages/orchestration/src/orchestration-api.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/orchestration/src/exos/chain-hub.js b/packages/orchestration/src/exos/chain-hub.js index ea47e5f2f67..0ee177a16b9 100644 --- a/packages/orchestration/src/exos/chain-hub.js +++ b/packages/orchestration/src/exos/chain-hub.js @@ -31,7 +31,7 @@ import { CosmosChainInfoShape, IBCConnectionInfoShape } from '../typeGuards.js'; * @property {string} baseName - name of issuing chain; e.g. cosmoshub * @property {Denom} baseDenom - e.g. uatom * @property {string} chainName - name of holding chain; e.g. agoric - * @property {Brand} [brand] - vbank brand, if registered + * @property {Brand<'nat'>} [brand] - vbank brand, if registered * @see {ChainHub} `registerAsset` method */ /** @type {TypedPattern} */ diff --git a/packages/orchestration/src/orchestration-api.ts b/packages/orchestration/src/orchestration-api.ts index 92c36edf3e2..b2b8856e930 100644 --- a/packages/orchestration/src/orchestration-api.ts +++ b/packages/orchestration/src/orchestration-api.ts @@ -44,7 +44,7 @@ export type Denom = string; // ibc/... or uist * In many cases, either a denom string or a local Brand can be used to * designate a remote token type. */ -export type DenomArg = Denom | Brand; +export type DenomArg = Denom | Brand<'nat'>; /** * Count of some fungible token on some blockchain. @@ -57,7 +57,7 @@ export type DenomAmount = { }; /** Amounts can be provided as pure data using denoms or as ERTP Amounts */ -export type AmountArg = DenomAmount | Amount; +export type AmountArg = DenomAmount | Amount<'nat'>; /** An address on some blockchain, e.g., cosmos, eth, etc. */ export type ChainAddress = { From be89ccbc3623c982d3e835e4e3066a1e8ac34533 Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Tue, 27 Aug 2024 16:33:54 -0700 Subject: [PATCH 03/14] feat(chainHubAdmin): registerAsset --- .../orchestration/src/exos/chain-hub-admin.js | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/packages/orchestration/src/exos/chain-hub-admin.js b/packages/orchestration/src/exos/chain-hub-admin.js index 527c7a70433..fd18f2b7d60 100644 --- a/packages/orchestration/src/exos/chain-hub-admin.js +++ b/packages/orchestration/src/exos/chain-hub-admin.js @@ -1,13 +1,14 @@ /* we expect promises to resolved promptly, */ /* eslint-disable no-restricted-syntax */ -import { M } from '@endo/patterns'; import { heapVowE } from '@agoric/vow/vat.js'; +import { M } from '@endo/patterns'; import { CosmosChainInfoShape } from '../typeGuards.js'; +import { DenomDetailShape } from './chain-hub.js'; /** * @import {Zone} from '@agoric/zone'; - * @import {CosmosChainInfo, IBCConnectionInfo} from '@agoric/orchestration'; - * @import {ChainHub} from './chain-hub.js'; + * @import {CosmosChainInfo, Denom, IBCConnectionInfo} from '@agoric/orchestration'; + * @import {ChainHub, DenomDetail} from './chain-hub.js'; */ /** @@ -28,6 +29,7 @@ export const prepareChainHubAdmin = (zone, chainHub) => { CosmosChainInfoShape, ConnectionInfoShape, ).returns(M.undefined()), + registerAsset: M.call(M.string(), DenomDetailShape).returns(M.promise()), }), { /** @@ -48,6 +50,19 @@ export const prepareChainHubAdmin = (zone, chainHub) => { connectionInfo, ); }, + /** + * Register an asset that may be held on a chain other than the issuing + * chain. + * + * @param {Denom} denom - on the holding chain, whose name is given in + * `detail.chainName` + * @param {DenomDetail} detail - chainName and baseName must be registered + */ + async registerAsset(denom, detail) { + // XXX async work necessary before the synchronous call + await heapVowE.when(chainHub.getChainInfo('agoric')); + chainHub.registerAsset(denom, detail); + }, }, ); return makeCreatorFacet; From e030acb06a2f99839d48db41a928695607624241 Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Wed, 28 Aug 2024 11:49:42 -0700 Subject: [PATCH 04/14] feat(chainHub): lookupDenom --- packages/orchestration/src/exos/chain-hub.js | 22 ++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/packages/orchestration/src/exos/chain-hub.js b/packages/orchestration/src/exos/chain-hub.js index 0ee177a16b9..e9e9a53259a 100644 --- a/packages/orchestration/src/exos/chain-hub.js +++ b/packages/orchestration/src/exos/chain-hub.js @@ -168,6 +168,7 @@ const ChainHubI = M.interface('ChainHub', { getChainsAndConnection: M.call(M.string(), M.string()).returns(VowShape), registerAsset: M.call(M.string(), DenomDetailShape).returns(), lookupAsset: M.call(M.string()).returns(DenomDetailShape), + lookupDenom: M.call(BrandShape).returns(M.or(M.string(), M.undefined())), }); /** @@ -199,6 +200,11 @@ export const makeChainHub = (agoricNames, vowTools) => { keyShape: M.string(), valueShape: DenomDetailShape, }); + /** @type {MapStore} */ + const brandDenoms = zone.mapStore('brandDenom', { + keyShape: BrandShape, + valueShape: M.string(), + }); const lookupChainInfo = vowTools.retriable( zone, @@ -380,15 +386,31 @@ export const makeChainHub = (agoricNames, vowTools) => { chainInfos.has(baseName) || Fail`must register chain ${q(baseName)} first`; denomDetails.init(denom, detail); + if (detail.brand) { + brandDenoms.init(detail.brand, denom); + } }, /** * Retrieve holding, issuing chain names etc. for a denom. * * @param {Denom} denom + * @returns {DenomDetail} */ lookupAsset(denom) { return denomDetails.get(denom); }, + /** + * Retrieve holding, issuing chain names etc. for a denom. + * + * @param {Brand} brand + * @returns {string | undefined} + */ + lookupDenom(brand) { + if (brandDenoms.has(brand)) { + return brandDenoms.get(brand); + } + return undefined; + }, }); return chainHub; From 6bdaf245b6e689f9b262d435df59f830384b73bb Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Tue, 27 Aug 2024 16:35:12 -0700 Subject: [PATCH 05/14] feat: lookupAsset in getBalance --- .../src/examples/stakeBld.contract.js | 18 +++++++++++++++--- .../src/exos/local-orchestration-account.js | 13 +++++++++---- .../src/utils/orchestrationAccount.js | 7 +++++-- .../test/examples/stake-bld.contract.test.ts | 7 +++++-- 4 files changed, 34 insertions(+), 11 deletions(-) diff --git a/packages/orchestration/src/examples/stakeBld.contract.js b/packages/orchestration/src/examples/stakeBld.contract.js index 7e75cbf62f9..2709d18d3e3 100644 --- a/packages/orchestration/src/examples/stakeBld.contract.js +++ b/packages/orchestration/src/examples/stakeBld.contract.js @@ -2,15 +2,16 @@ * @file Stake BLD contract */ import { makeTracer } from '@agoric/internal'; +import { heapVowE as E, prepareVowTools } from '@agoric/vow/vat.js'; import { prepareRecorderKitMakers } from '@agoric/zoe/src/contractSupport/recorder.js'; import { withdrawFromSeat } from '@agoric/zoe/src/contractSupport/zoeHelpers.js'; import { InvitationShape } from '@agoric/zoe/src/typeGuards.js'; import { makeDurableZone } from '@agoric/zone/durable.js'; -import { prepareVowTools, heapVowE as E } from '@agoric/vow/vat.js'; import { deeplyFulfilled } from '@endo/marshal'; import { M } from '@endo/patterns'; -import { prepareLocalOrchestrationAccountKit } from '../exos/local-orchestration-account.js'; import { makeChainHub } from '../exos/chain-hub.js'; +import { prepareLocalOrchestrationAccountKit } from '../exos/local-orchestration-account.js'; +import fetchedChainInfo from '../fetched-chain-info.js'; /** * @import {NameHub} from '@agoric/vats'; @@ -41,13 +42,15 @@ export const start = async (zcf, privateArgs, baggage) => { ); const vowTools = prepareVowTools(zone.subZone('vows')); + const chainHub = makeChainHub(privateArgs.agoricNames, vowTools); + const makeLocalOrchestrationAccountKit = prepareLocalOrchestrationAccountKit( zone, makeRecorderKit, zcf, privateArgs.timerService, vowTools, - makeChainHub(privateArgs.agoricNames, vowTools), + chainHub, ); // ---------------- @@ -56,6 +59,15 @@ export const start = async (zcf, privateArgs, baggage) => { const BLD = zcf.getTerms().brands.In; const bldAmountShape = await E(BLD).getAmountShape(); + // XXX big dependency (59KB) but in production will probably already be registered in agoricNames + chainHub.registerChain('agoric', fetchedChainInfo.agoric); + chainHub.registerAsset('ubld', { + baseName: 'agoric', + baseDenom: 'ubld', + brand: BLD, + chainName: 'agoric', + }); + async function makeLocalAccountKit() { const account = await E(privateArgs.localchain).makeAccount(); const address = await E(account).getAddress(); diff --git a/packages/orchestration/src/exos/local-orchestration-account.js b/packages/orchestration/src/exos/local-orchestration-account.js index 5bfe93eb92c..f83a98e3861 100644 --- a/packages/orchestration/src/exos/local-orchestration-account.js +++ b/packages/orchestration/src/exos/local-orchestration-account.js @@ -392,12 +392,17 @@ export const prepareLocalOrchestrationAccountKit = ( * @type {HostOf} */ getBalance(denomArg) { - // FIXME look up real values - // UNTIL https://github.com/Agoric/agoric-sdk/issues/9211 const [brand, denom] = typeof denomArg === 'string' - ? [/** @type {any} */ (null), denomArg] - : [denomArg, 'FIXME']; + ? [chainHub.lookupAsset(denomArg).brand, denomArg] + : [denomArg, chainHub.lookupDenom(denomArg)]; + + if (!brand) { + throw Fail`No brand for ${denomArg}`; + } + if (!denom) { + throw Fail`No denom for ${denomArg}`; + } return watch( E(this.state.account).getBalance(brand), diff --git a/packages/orchestration/src/utils/orchestrationAccount.js b/packages/orchestration/src/utils/orchestrationAccount.js index a227d7ea343..242482bce60 100644 --- a/packages/orchestration/src/utils/orchestrationAccount.js +++ b/packages/orchestration/src/utils/orchestrationAccount.js @@ -1,7 +1,8 @@ -import { M } from '@endo/patterns'; +import { BrandShape } from '@agoric/ertp'; import { Shape as NetworkShape } from '@agoric/network'; import { VowShape } from '@agoric/vow'; import { TopicsRecordShape } from '@agoric/zoe/src/contractSupport/topics.js'; +import { M } from '@endo/patterns'; import { AmountArgShape, ChainAddressShape, @@ -16,7 +17,9 @@ const { Vow$ } = NetworkShape; // TODO #9611 /** @see {OrchestrationAccountI} */ export const orchestrationAccountMethods = { getAddress: M.call().returns(ChainAddressShape), - getBalance: M.call(M.any()).returns(Vow$(DenomAmountShape)), + getBalance: M.call(M.or(BrandShape, M.string())).returns( + Vow$(DenomAmountShape), + ), getBalances: M.call().returns(Vow$(M.arrayOf(DenomAmountShape))), send: M.call(ChainAddressShape, AmountArgShape).returns(VowShape), sendAll: M.call(ChainAddressShape, M.arrayOf(AmountArgShape)).returns( diff --git a/packages/orchestration/test/examples/stake-bld.contract.test.ts b/packages/orchestration/test/examples/stake-bld.contract.test.ts index ef485c053d4..736e9781fad 100644 --- a/packages/orchestration/test/examples/stake-bld.contract.test.ts +++ b/packages/orchestration/test/examples/stake-bld.contract.test.ts @@ -57,8 +57,11 @@ test('makeAccount, deposit, withdraw', async t => { const depositResp = await E(account).deposit( await utils.pourPayment(bld.units(100)), ); - // FIXME #9211 - // t.deepEqual(await E(account).getBalance('ubld'), bld.units(100)); + t.deepEqual(await E(account).getBalance('ubld'), { + denom: 'ubld', + value: bld.units(100).value, + }); + // XXX races in the bridge await eventLoopIteration(); From 1840f37b539afe90c4f8f804c379c668cb947bc6 Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Wed, 28 Aug 2024 11:50:23 -0700 Subject: [PATCH 06/14] feat: amountToCoin lookupDenom --- .../src/exos/cosmos-orchestration-account.js | 16 +++++++++------- .../exos/cosmos-orchestration-account.test.ts | 7 ++++--- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/packages/orchestration/src/exos/cosmos-orchestration-account.js b/packages/orchestration/src/exos/cosmos-orchestration-account.js index 8af61ed745d..046dc96c618 100644 --- a/packages/orchestration/src/exos/cosmos-orchestration-account.js +++ b/packages/orchestration/src/exos/cosmos-orchestration-account.js @@ -17,7 +17,7 @@ import { import { Any } from '@agoric/cosmic-proto/google/protobuf/any.js'; import { MsgSend } from '@agoric/cosmic-proto/cosmos/bank/v1beta1/tx.js'; import { MsgTransfer } from '@agoric/cosmic-proto/ibc/applications/transfer/v1/tx.js'; -import { makeTracer } from '@agoric/internal'; +import { makeTracer, NonNullish } from '@agoric/internal'; import { Shape as NetworkShape } from '@agoric/network'; import { M } from '@agoric/vat-data'; import { VowShape } from '@agoric/vow'; @@ -242,13 +242,15 @@ export const prepareCosmosOrchestrationAccountKit = ( * @returns {Coin} */ amountToCoin(amount) { - if (!('denom' in amount)) { - // FIXME(#9211) look up values from brands - trace('TODO #9211: handle brand', amount); - throw Fail`Brands not currently supported.`; - } + const denom = + 'denom' in amount + ? amount.denom + : NonNullish( + chainHub.lookupDenom(amount.brand), + `No denomination for brand ${amount.brand}`, + ); return harden({ - denom: amount.denom, + denom, amount: String(amount.value), }); }, diff --git a/packages/orchestration/test/exos/cosmos-orchestration-account.test.ts b/packages/orchestration/test/exos/cosmos-orchestration-account.test.ts index 497976a6f33..667292e2104 100644 --- a/packages/orchestration/test/exos/cosmos-orchestration-account.test.ts +++ b/packages/orchestration/test/exos/cosmos-orchestration-account.test.ts @@ -60,8 +60,8 @@ test('CosmosOrchestrationAccount - send (to addr on same chain)', async t => { // ertp amounts not supported await t.throwsAsync( E(account).send(toAddress, ist.make(10n) as AmountArg), - // TODO #9211 lookup denom from brand - { message: 'Brands not currently supported.' }, + // TODO #9211 register asset before sending + { message: 'No denomination for brand [object Alleged: IST brand]' }, ); // multi-send (sendAll) @@ -260,7 +260,8 @@ test('CosmosOrchestrationAccount - transfer', async t => { t.log("transfer doesn't support ERTP brands yet. see #9211"); await t.throwsAsync(E(account).transfer(ist.make(10n), mockDestination), { - message: 'Brands not currently supported.', + // TODO #9211 register asset before transfer + message: 'No denomination for brand [object Alleged: IST brand]', }); t.log('transfer timeout error recieved and handled from the bridge'); From 1bf09d140b27310715a4a0923b480d875e28949e Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Wed, 28 Aug 2024 12:32:48 -0700 Subject: [PATCH 07/14] feat: amount coercion utils --- .../src/exos/cosmos-orchestration-account.js | 8 ++- packages/orchestration/src/utils/amounts.js | 53 +++++++++++++++++++ 2 files changed, 56 insertions(+), 5 deletions(-) create mode 100644 packages/orchestration/src/utils/amounts.js diff --git a/packages/orchestration/src/exos/cosmos-orchestration-account.js b/packages/orchestration/src/exos/cosmos-orchestration-account.js index 046dc96c618..6a2ac43ba75 100644 --- a/packages/orchestration/src/exos/cosmos-orchestration-account.js +++ b/packages/orchestration/src/exos/cosmos-orchestration-account.js @@ -4,6 +4,7 @@ import { QueryBalanceRequest, QueryBalanceResponse, } from '@agoric/cosmic-proto/cosmos/bank/v1beta1/query.js'; +import { MsgSend } from '@agoric/cosmic-proto/cosmos/bank/v1beta1/tx.js'; import { MsgWithdrawDelegatorReward, MsgWithdrawDelegatorRewardResponse, @@ -15,7 +16,6 @@ import { MsgUndelegateResponse, } from '@agoric/cosmic-proto/cosmos/staking/v1beta1/tx.js'; import { Any } from '@agoric/cosmic-proto/google/protobuf/any.js'; -import { MsgSend } from '@agoric/cosmic-proto/cosmos/bank/v1beta1/tx.js'; import { MsgTransfer } from '@agoric/cosmic-proto/ibc/applications/transfer/v1/tx.js'; import { makeTracer, NonNullish } from '@agoric/internal'; import { Shape as NetworkShape } from '@agoric/network'; @@ -31,6 +31,7 @@ import { DenomAmountShape, IBCTransferOptionsShape, } from '../typeGuards.js'; +import { coerceDenom } from '../utils/amounts.js'; import { maxClockSkew, tryDecodeResponse } from '../utils/cosmos.js'; import { orchestrationAccountMethods } from '../utils/orchestrationAccount.js'; import { makeTimestampHelper } from '../utils/time.js'; @@ -576,14 +577,11 @@ export const prepareCosmosOrchestrationAccountKit = ( if (!icqConnection) { throw Fail`Queries not available for chain ${chainAddress.chainId}`; } - // TODO #9211 lookup denom from brand - assert.typeof(denom, 'string'); - const results = E(icqConnection).query([ toRequestQueryJson( QueryBalanceRequest.toProtoMsg({ address: chainAddress.value, - denom, + denom: coerceDenom(chainHub, denom), }), ), ]); diff --git a/packages/orchestration/src/utils/amounts.js b/packages/orchestration/src/utils/amounts.js new file mode 100644 index 00000000000..840538f244d --- /dev/null +++ b/packages/orchestration/src/utils/amounts.js @@ -0,0 +1,53 @@ +import { makeError } from '@endo/errors'; + +/** + * @import {ChainHub} from "../types.js"; + * @import {AmountArg, Denom, DenomAmount, DenomArg} from "../orchestration-api.js"; + * @import {Coin} from '@agoric/cosmic-proto/cosmos/base/v1beta1/coin.js'; + */ + +/** + * @param {ChainHub} chainHub + * @param {DenomArg} denomArg + * @returns {Denom} + */ +export const coerceDenom = (chainHub, denomArg) => { + if (typeof denomArg === 'string') { + return denomArg; + } + const denom = chainHub.lookupDenom(denomArg); + if (!denom) { + throw makeError(`No denomination for brand ${denomArg}`); + } + return denom; +}; + +/** + * @param {ChainHub} chainHub + * @param {DenomAmount | Amount<'nat'>} amount + * @returns {DenomAmount} + */ +export const coerceDenomAmount = (chainHub, amount) => { + if ('denom' in amount) { + return amount; + } + const denom = coerceDenom(chainHub, amount.brand); + return harden({ + denom, + value: amount.value, + }); +}; + +/** + * @param {ChainHub} chainHub + * @param {AmountArg | Amount<'nat'>} amount + * @returns {Coin} + */ +export const coerceCoin = (chainHub, amount) => { + const denom = + 'denom' in amount ? amount.denom : coerceDenom(chainHub, amount.brand); + return harden({ + denom, + amount: String(amount.value), + }); +}; From 2659dc04c29a5a0728d74dad090da1e2f213b2ab Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Wed, 28 Aug 2024 16:22:20 -0700 Subject: [PATCH 08/14] test: compute hash in auto-stake-it example --- multichain-testing/test/auto-stake-it.test.ts | 36 ++++--------------- .../src/examples/auto-stake-it.flows.js | 11 +++--- .../examples/auto-stake-it.contract.test.ts | 8 ++--- 3 files changed, 13 insertions(+), 42 deletions(-) diff --git a/multichain-testing/test/auto-stake-it.test.ts b/multichain-testing/test/auto-stake-it.test.ts index 69af5caf669..16e32bdf9a4 100644 --- a/multichain-testing/test/auto-stake-it.test.ts +++ b/multichain-testing/test/auto-stake-it.test.ts @@ -1,16 +1,16 @@ +import type { CosmosChainInfo } from '@agoric/orchestration'; import anyTest from '@endo/ses-ava/prepare-endo.js'; import type { ExecutionContext, TestFn } from 'ava'; import { useChain } from 'starshipjs'; -import type { CosmosChainInfo, IBCConnectionInfo } from '@agoric/orchestration'; -import type { SetupContextWithWallets } from './support.js'; -import { chainConfig, commonSetup } from './support.js'; -import { makeQueryClient } from '../tools/query.js'; -import { makeDoOffer } from '../tools/e2e-tools.js'; import chainInfo from '../starship-chain-info.js'; +import { makeDoOffer } from '../tools/e2e-tools.js'; import { createFundedWalletAndClient, makeIBCTransferMsg, } from '../tools/ibc-transfer.js'; +import { makeQueryClient } from '../tools/query.js'; +import type { SetupContextWithWallets } from './support.js'; +import { chainConfig, commonSetup } from './support.js'; const test = anyTest as TestFn; @@ -82,6 +82,7 @@ const makeFundAndTransfer = (t: ExecutionContext) => { const autoStakeItScenario = test.macro({ title: (_, chainName: string) => `auto-stake-it on ${chainName}`, exec: async (t, chainName: string) => { + // 1. setup const { wallets, vstorageClient, @@ -91,36 +92,12 @@ const autoStakeItScenario = test.macro({ const fundAndTransfer = makeFundAndTransfer(t); - // 1. Send initial tokens so denom is available (debatably necessary, but - // allows us to trace the denom until we have ibc denoms in chainInfo) - const agAdminAddr = wallets['agoricAdmin']; - console.log('Sending tokens to', agAdminAddr, `from ${chainName}`); - await fundAndTransfer(chainName, agAdminAddr); - // 2. Find 'stakingDenom' denom on agoric - const agoricConns = chainInfo['agoric'].connections as Record< - string, - IBCConnectionInfo - >; const remoteChainInfo = (chainInfo as Record)[ chainName ]; - // const remoteChainId = remoteChainInfo.chain.chain_id; - // const agoricToRemoteConn = agoricConns[remoteChainId]; - const { portId, channelId } = - agoricConns[remoteChainInfo.chainId].transferChannel; - const agoricQueryClient = makeQueryClient( - await useChain('agoric').getRestEndpoint(), - ); const stakingDenom = remoteChainInfo?.stakingTokens?.[0].denom; if (!stakingDenom) throw Error(`staking denom found for ${chainName}`); - const { hash } = await retryUntilCondition( - () => - agoricQueryClient.queryDenom(`/${portId}/${channelId}`, stakingDenom), - denomTrace => !!denomTrace.hash, - `local denom hash for ${stakingDenom} found`, - ); - t.log(`found ibc denom hash for ${stakingDenom}:`, hash); // 3. Find a remoteChain validator to delegate to const remoteQueryClient = makeQueryClient( @@ -161,7 +138,6 @@ const autoStakeItScenario = test.macro({ encoding: 'bech32', chainId: remoteChainInfo.chainId, }, - localDenom: `ibc/${hash}`, }, proposal: {}, }); diff --git a/packages/orchestration/src/examples/auto-stake-it.flows.js b/packages/orchestration/src/examples/auto-stake-it.flows.js index 9904ad5fb39..74d1fc839a7 100644 --- a/packages/orchestration/src/examples/auto-stake-it.flows.js +++ b/packages/orchestration/src/examples/auto-stake-it.flows.js @@ -1,4 +1,5 @@ import { Fail } from '@endo/errors'; +import { denomHash } from '../utils/denomHash.js'; /** * @import {ResolvedPublicTopic} from '@agoric/zoe/src/contractSupport/topics.js'; @@ -21,19 +22,13 @@ import { Fail } from '@endo/errors'; * @param {{ * chainName: string; * validator: CosmosValidatorAddress; - * localDenom: Denom; * }} offerArgs */ export const makeAccounts = async ( orch, { makeStakingTap, makePortfolioHolder, chainHub }, seat, - { - chainName, - validator, - // TODO localDenom is user supplied, until #9211 - localDenom, - }, + { chainName, validator }, ) => { seat.exit(); // no funds exchanged const [agoric, remoteChain] = await Promise.all([ @@ -65,6 +60,8 @@ export const makeAccounts = async ( ); assert(transferChannel.counterPartyChannelId, 'unable to find sourceChannel'); + const localDenom = `ibc/${denomHash({ denom: remoteDenom, channelId: transferChannel.channelId })}`; + // Every time the `localAccount` receives `remoteDenom` over IBC, delegate it. const tap = makeStakingTap({ localAccount, diff --git a/packages/orchestration/test/examples/auto-stake-it.contract.test.ts b/packages/orchestration/test/examples/auto-stake-it.contract.test.ts index 0401d00adb6..9e02dc48639 100644 --- a/packages/orchestration/test/examples/auto-stake-it.contract.test.ts +++ b/packages/orchestration/test/examples/auto-stake-it.contract.test.ts @@ -51,8 +51,6 @@ test('auto-stake-it - make accounts, register tap, return invitationMakers', asy value: 'cosmosvaloper1test', encoding: 'bech32', }, - // TODO user supplied until #9211 - localDenom: 'ibc/fakeuatomhash', }); const result = await heapVowE(userSeat).getOfferResult(); @@ -114,7 +112,9 @@ test('auto-stake-it - make accounts, register tap, return invitationMakers', asy receiver: 'cosmos1test', sender: execAddr, sourceChannel: 'channel-5', - token: { amount: '10', denom: 'ibc/fakeuatomhash' }, + token: { + amount: '10', + }, }, 'tokens transferred from LOA to COA', ); @@ -139,8 +139,6 @@ test('auto-stake-it - make accounts, register tap, return invitationMakers', asy value: 'cosmosvaloper1test', encoding: 'bech32', }, - // TODO user supplied until #9211 - localDenom: 'ibc/fakeuatomhash', }); const { publicSubscribers: pubSubs2 } = await heapVowE(userSeat2).getOfferResult(); From d2b46ae1289a9010be3519341ba31dad6f0589ba Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Thu, 29 Aug 2024 10:35:00 -0700 Subject: [PATCH 09/14] docs: distinguish ibc-mocks --- packages/orchestration/test/ibc-mocks.ts | 1 + packages/orchestration/tools/ibc-mocks.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/orchestration/test/ibc-mocks.ts b/packages/orchestration/test/ibc-mocks.ts index 86ef8862cf2..6992227ee34 100644 --- a/packages/orchestration/test/ibc-mocks.ts +++ b/packages/orchestration/test/ibc-mocks.ts @@ -1,3 +1,4 @@ +/** @file The mocks used in these tests */ import { QueryBalanceRequest, QueryBalanceResponse, diff --git a/packages/orchestration/tools/ibc-mocks.ts b/packages/orchestration/tools/ibc-mocks.ts index a6f66a7164d..65266645e5d 100644 --- a/packages/orchestration/tools/ibc-mocks.ts +++ b/packages/orchestration/tools/ibc-mocks.ts @@ -1,3 +1,4 @@ +/** @file Tools to support making IBC mocks */ import { Any } from '@agoric/cosmic-proto/google/protobuf/any.js'; import { CosmosResponse } from '@agoric/cosmic-proto/icq/v1/packet.js'; import { From 0c2b5ce8004d54e6c3d1a0932e6d2e80550c0904 Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Thu, 29 Aug 2024 10:34:48 -0700 Subject: [PATCH 10/14] test: consistent titles --- .../test/examples/auto-stake-it.contract.test.ts | 2 +- .../test/exos/cosmos-orchestration-account.test.ts | 6 +++--- packages/orchestration/test/ibc-mocks.test.ts | 10 +++++----- packages/orchestration/test/network-fakes.test.ts | 6 +++--- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/orchestration/test/examples/auto-stake-it.contract.test.ts b/packages/orchestration/test/examples/auto-stake-it.contract.test.ts index 9e02dc48639..53b184ccc37 100644 --- a/packages/orchestration/test/examples/auto-stake-it.contract.test.ts +++ b/packages/orchestration/test/examples/auto-stake-it.contract.test.ts @@ -19,7 +19,7 @@ const contractFile = `${dirname}/../../src/examples/${contractName}.contract.js` type StartFn = typeof import('../../src/examples/auto-stake-it.contract.js').start; -test('auto-stake-it - make accounts, register tap, return invitationMakers', async t => { +test('make accounts, register tap, return invitationMakers', async t => { t.log('bootstrap, orchestration core-eval'); const { bootstrap: { storage }, diff --git a/packages/orchestration/test/exos/cosmos-orchestration-account.test.ts b/packages/orchestration/test/exos/cosmos-orchestration-account.test.ts index 667292e2104..21fd111991b 100644 --- a/packages/orchestration/test/exos/cosmos-orchestration-account.test.ts +++ b/packages/orchestration/test/exos/cosmos-orchestration-account.test.ts @@ -25,7 +25,7 @@ test.beforeEach(async t => { t.context = await commonSetup(t); }); -test('CosmosOrchestrationAccount - send (to addr on same chain)', async t => { +test('send (to addr on same chain)', async t => { const { bootstrap, brands: { ist }, @@ -82,7 +82,7 @@ test('CosmosOrchestrationAccount - send (to addr on same chain)', async t => { ); }); -test('CosmosOrchestrationAccount - transfer', async t => { +test('transfer', async t => { const { brands: { ist }, bootstrap, @@ -285,7 +285,7 @@ test('CosmosOrchestrationAccount - transfer', async t => { ); }); -test('CosmosOrchestrationAccount - not yet implemented', async t => { +test('not yet implemented', async t => { const { bootstrap } = await commonSetup(t); const makeTestCOAKit = prepareMakeTestCOAKit(t, bootstrap); const account = await makeTestCOAKit(); diff --git a/packages/orchestration/test/ibc-mocks.test.ts b/packages/orchestration/test/ibc-mocks.test.ts index 30a1d8e3c25..0206c793981 100644 --- a/packages/orchestration/test/ibc-mocks.test.ts +++ b/packages/orchestration/test/ibc-mocks.test.ts @@ -15,14 +15,14 @@ import { buildQueryPacketString, } from '../tools/ibc-mocks.js'; -test('ibc-mocks - buildMsgResponseString matches observed values in e2e testing', t => { +test('buildMsgResponseString matches observed values in e2e testing', t => { t.is( buildMsgResponseString(MsgDelegateResponse, {}), 'eyJyZXN1bHQiOiJFaTBLS3k5amIzTnRiM011YzNSaGEybHVaeTUyTVdKbGRHRXhMazF6WjBSbGJHVm5ZWFJsVW1WemNHOXVjMlU9In0=', ); }); -test('ibc-mocks - buildMsgErrorString matches observed values in e2e testing', t => { +test('buildMsgErrorString matches observed values in e2e testing', t => { t.is( buildMsgErrorString(), 'eyJlcnJvciI6IkFCQ0kgY29kZTogNTogZXJyb3IgaGFuZGxpbmcgcGFja2V0OiBzZWUgZXZlbnRzIGZvciBkZXRhaWxzIn0=', @@ -37,7 +37,7 @@ test('ibc-mocks - buildMsgErrorString matches observed values in e2e testing', t ); }); -test('ibcMocks - buildQueryResponseString matches observed values in e2e testing', t => { +test('buildQueryResponseString matches observed values in e2e testing', t => { t.is( buildQueryResponseString(QueryBalanceResponse, { balance: { amount: '0', denom: 'uatom' }, @@ -47,7 +47,7 @@ test('ibcMocks - buildQueryResponseString matches observed values in e2e testing ); }); -test('ibcMocks - build Tx Packet', t => { +test('build Tx Packet', t => { t.is( buildTxPacketString([ MsgDelegate.toProtoMsg({ @@ -63,7 +63,7 @@ test('ibcMocks - build Tx Packet', t => { ); }); -test('ibcMocks - build Query Packet', t => { +test('build Query Packet', t => { t.is( buildQueryPacketString([ QueryBalanceRequest.toProtoMsg({ diff --git a/packages/orchestration/test/network-fakes.test.ts b/packages/orchestration/test/network-fakes.test.ts index 9c70d14bb71..937807153b9 100644 --- a/packages/orchestration/test/network-fakes.test.ts +++ b/packages/orchestration/test/network-fakes.test.ts @@ -16,7 +16,7 @@ test.before(async t => { await t.context.setupIBCProtocol(); }); -test('network fakes - echo connection', async t => { +test('echo connection', async t => { const { portAllocator, networkVat } = t.context; // Allocate an echo port @@ -36,13 +36,13 @@ test('network fakes - echo connection', async t => { t.is(response, message, 'Echo returns the same message'); }); -test('network fakes - port allocator', async t => { +test('port allocator', async t => { const { portAllocator } = t.context; const customPort = await E(portAllocator).allocateCustomIBCPort('test-port'); t.is(await E(customPort).getLocalAddress(), '/ibc-port/custom-test-port'); }); -test('network fakes - ibc connection', async t => { +test('ibc connection', async t => { const { portAllocator } = t.context; // allocate ICA controller port and connect to remote chain From 6755cac4ff92c9b211feb70f9caaca5e9e0b911c Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Wed, 28 Aug 2024 12:33:40 -0700 Subject: [PATCH 11/14] feat: LCA looks up denoms --- .../test/bootstrapTests/orchestration.test.ts | 3 +- .../src/exos/cosmos-orchestration-account.js | 16 ++---- .../src/exos/local-orchestration-account.js | 32 ++++-------- .../local-orchestration-account-kit.test.ts | 51 +++++++++---------- .../test/exos/make-test-loa-kit.ts | 21 ++++---- .../test/exos/portfolio-holder-kit.test.ts | 10 ++-- packages/orchestration/test/supports.ts | 25 +++++++++ 7 files changed, 79 insertions(+), 79 deletions(-) diff --git a/packages/boot/test/bootstrapTests/orchestration.test.ts b/packages/boot/test/bootstrapTests/orchestration.test.ts index cea3574fe42..c619b911af4 100644 --- a/packages/boot/test/bootstrapTests/orchestration.test.ts +++ b/packages/boot/test/bootstrapTests/orchestration.test.ts @@ -240,9 +240,8 @@ test.serial('stakeAtom - smart wallet', async t => { proposal: {}, }), { - message: 'Brands not currently supported.', + message: 'No denomination for brand [object Alleged: ATOM brand]', }, - 'brands not currently supported', ); }); diff --git a/packages/orchestration/src/exos/cosmos-orchestration-account.js b/packages/orchestration/src/exos/cosmos-orchestration-account.js index 6a2ac43ba75..47d4f792d45 100644 --- a/packages/orchestration/src/exos/cosmos-orchestration-account.js +++ b/packages/orchestration/src/exos/cosmos-orchestration-account.js @@ -17,7 +17,7 @@ import { } from '@agoric/cosmic-proto/cosmos/staking/v1beta1/tx.js'; import { Any } from '@agoric/cosmic-proto/google/protobuf/any.js'; import { MsgTransfer } from '@agoric/cosmic-proto/ibc/applications/transfer/v1/tx.js'; -import { makeTracer, NonNullish } from '@agoric/internal'; +import { makeTracer } from '@agoric/internal'; import { Shape as NetworkShape } from '@agoric/network'; import { M } from '@agoric/vat-data'; import { VowShape } from '@agoric/vow'; @@ -31,7 +31,7 @@ import { DenomAmountShape, IBCTransferOptionsShape, } from '../typeGuards.js'; -import { coerceDenom } from '../utils/amounts.js'; +import { coerceCoin, coerceDenom } from '../utils/amounts.js'; import { maxClockSkew, tryDecodeResponse } from '../utils/cosmos.js'; import { orchestrationAccountMethods } from '../utils/orchestrationAccount.js'; import { makeTimestampHelper } from '../utils/time.js'; @@ -243,17 +243,7 @@ export const prepareCosmosOrchestrationAccountKit = ( * @returns {Coin} */ amountToCoin(amount) { - const denom = - 'denom' in amount - ? amount.denom - : NonNullish( - chainHub.lookupDenom(amount.brand), - `No denomination for brand ${amount.brand}`, - ); - return harden({ - denom, - amount: String(amount.value), - }); + return coerceCoin(chainHub, amount); }, }, balanceQueryWatcher: { diff --git a/packages/orchestration/src/exos/local-orchestration-account.js b/packages/orchestration/src/exos/local-orchestration-account.js index f83a98e3861..651f7c3361b 100644 --- a/packages/orchestration/src/exos/local-orchestration-account.js +++ b/packages/orchestration/src/exos/local-orchestration-account.js @@ -20,6 +20,7 @@ import { orchestrationAccountMethods } from '../utils/orchestrationAccount.js'; 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 {HostOf} from '@agoric/async-flow'; @@ -175,15 +176,7 @@ export const prepareLocalOrchestrationAccountKit = ( * @returns {Coin} */ amountToCoin(amount) { - if (!('denom' in amount)) { - // FIXME(#9211) look up values from brands - trace('TODO #9211: handle brand', amount); - throw Fail`Brands not currently supported.`; - } - return harden({ - denom: amount.denom, - amount: String(amount.value), - }); + return coerceCoin(chainHub, amount); }, }, invitationMakers: { @@ -440,13 +433,10 @@ export const prepareLocalOrchestrationAccountKit = ( * @param {Amount<'nat'>} ertpAmount */ delegate(validatorAddress, ertpAmount) { - // TODO #9211 lookup denom from brand - const amount = { - amount: String(ertpAmount.value), - denom: 'ubld', - }; const { account: lca } = this.state; + const amount = coerceCoin(chainHub, ertpAmount); + return watch( E(lca).executeTx([ typedJson('/cosmos.staking.v1beta1.MsgDelegate', { @@ -465,11 +455,7 @@ export const prepareLocalOrchestrationAccountKit = ( * @returns {Vow} */ undelegate(validatorAddress, ertpAmount) { - // TODO #9211 lookup denom from brand - const amount = { - amount: String(ertpAmount.value), - denom: 'ubld', - }; + const amount = coerceCoin(chainHub, ertpAmount); const { account: lca } = this.state; return watch( E(lca).executeTx([ @@ -560,8 +546,6 @@ export const prepareLocalOrchestrationAccountKit = ( transfer(amount, destination, opts) { return asVow(() => { trace('Transferring funds from LCA over IBC'); - // TODO #9211 lookup denom from brand - if ('brand' in amount) throw Fail`ERTP Amounts not yet supported`; const connectionInfoV = watch( chainHub.getConnectionInfo( @@ -583,7 +567,11 @@ export const prepareLocalOrchestrationAccountKit = ( const resultV = watch( allVows([connectionInfoV, timeoutTimestampVowOrValue]), this.facets.transferWatcher, - { opts, amount, destination }, + { + opts, + amount: coerceDenomAmount(chainHub, amount), + destination, + }, ); return resultV; }); 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 5930fe84ebf..54bc1ae4274 100644 --- a/packages/orchestration/test/exos/local-orchestration-account-kit.test.ts +++ b/packages/orchestration/test/exos/local-orchestration-account-kit.test.ts @@ -2,20 +2,20 @@ import { test } from '@agoric/zoe/tools/prepare-test-env-ava.js'; import { AmountMath } from '@agoric/ertp'; import { eventLoopIteration } from '@agoric/internal/src/testing-utils.js'; -import { heapVowE as VE } from '@agoric/vow/vat.js'; import { TargetApp } from '@agoric/vats/src/bridge-target.js'; import { SIMULATED_ERRORS } from '@agoric/vats/tools/fake-bridge.js'; +import { heapVowE as VE } from '@agoric/vow/vat.js'; import { ChainAddress, type AmountArg } from '../../src/orchestration-api.js'; +import { maxClockSkew } from '../../src/utils/cosmos.js'; import { NANOSECONDS_PER_SECOND } from '../../src/utils/time.js'; -import { commonSetup } from '../supports.js'; +import { buildVTransferEvent } from '../../tools/ibc-mocks.js'; import { UNBOND_PERIOD_SECONDS } from '../ibc-mocks.js'; -import { maxClockSkew } from '../../src/utils/cosmos.js'; +import { commonSetup } from '../supports.js'; import { prepareMakeTestLOAKit } from './make-test-loa-kit.js'; -import { buildVTransferEvent } from '../../tools/ibc-mocks.js'; test('deposit, withdraw', async t => { const common = await commonSetup(t); - const makeTestLOAKit = prepareMakeTestLOAKit(t, common.bootstrap); + const makeTestLOAKit = prepareMakeTestLOAKit(t, common); const account = await makeTestLOAKit(); const { @@ -27,8 +27,10 @@ test('deposit, withdraw', async t => { t.log('deposit 100 bld to account'); await VE(account).deposit(oneHundredStakePmt); - // FIXME #9211 - // t.deepEqual(await E(account).getBalance('ubld'), stake.units(100)); + t.deepEqual(await VE(account).getBalance('ubld'), { + denom: 'ubld', + value: stake.units(100).value, + }); // XXX races in the bridge await eventLoopIteration(); @@ -58,7 +60,7 @@ test('deposit, withdraw', async t => { test('delegate, undelegate', async t => { const common = await commonSetup(t); - const makeTestLOAKit = prepareMakeTestLOAKit(t, common.bootstrap); + const makeTestLOAKit = prepareMakeTestLOAKit(t, common); const account = await makeTestLOAKit(); const { @@ -94,7 +96,7 @@ test('delegate, undelegate', async t => { test('transfer', async t => { const common = await commonSetup(t); - const makeTestLOAKit = prepareMakeTestLOAKit(t, common.bootstrap); + const makeTestLOAKit = prepareMakeTestLOAKit(t, common); const account = await makeTestLOAKit(); const { value: sender } = await VE(account).getAddress(); @@ -111,8 +113,10 @@ test('transfer', async t => { t.log('deposit 100 bld to account'); await VE(account).deposit(oneHundredStakePmt); - // FIXME #9211 - // t.deepEqual(await E(account).getBalance('ubld'), stake.units(100)); + t.deepEqual(await VE(account).getBalance('ubld'), { + denom: 'ubld', + value: stake.units(100).value, + }); const destination: ChainAddress = { chainId: 'cosmoshub-4', @@ -144,12 +148,6 @@ test('transfer', async t => { return { transferP }; }; - // TODO #9211, support ERTP amounts - t.log('ERTP Amounts not yet supported for AmountArg'); - await t.throwsAsync(() => VE(account).transfer(stake.units(1), destination), { - message: 'ERTP Amounts not yet supported', - }); - t.log('.transfer() 1 bld to cosmos using DenomAmount'); const { transferP } = await startTransfer( { denom: 'ubld', value: 1_000_000n }, @@ -247,7 +245,7 @@ test('transfer', async t => { test('monitor transfers', async t => { const common = await commonSetup(t); - const makeTestLOAKit = prepareMakeTestLOAKit(t, common.bootstrap); + const makeTestLOAKit = prepareMakeTestLOAKit(t, common); const account = await makeTestLOAKit(); const { mocks: { transferBridge }, @@ -290,15 +288,15 @@ test('monitor transfers', async t => { }); test('send', async t => { - const { - bootstrap, - brands: { bld: stake, ist: stable }, - utils: { pourPayment, inspectLocalBridge }, - } = await commonSetup(t); - const makeTestLOAKit = prepareMakeTestLOAKit(t, bootstrap); + const common = await commonSetup(t); + const makeTestLOAKit = prepareMakeTestLOAKit(t, common); const account = await makeTestLOAKit(); t.truthy(account, 'account is returned'); + const { + brands: { bld: stake, ist: stable }, + utils: { pourPayment, inspectLocalBridge }, + } = common; const oneHundredStakePmt = await pourPayment(stake.units(100)); const oneHundredStablePmt = await pourPayment(stable.units(100)); t.log('deposit 100 bld to account'); @@ -313,10 +311,7 @@ test('send', async t => { }; t.log(`send 10 bld to ${toAddress.value}`); - await t.throwsAsync(VE(account).send(toAddress, stake.units(10)), { - message: 'Brands not currently supported.', - }); - await VE(account).send(toAddress, { denom: 'ubld', value: 10_000_000n }); + await VE(account).send(toAddress, stake.units(10)); // this would normally fail since we do not have ibc/1234 in our wallet, // but the mocked localchain bridge doesn't currently know about balances diff --git a/packages/orchestration/test/exos/make-test-loa-kit.ts b/packages/orchestration/test/exos/make-test-loa-kit.ts index 018aa638fd5..2b909fab6ba 100644 --- a/packages/orchestration/test/exos/make-test-loa-kit.ts +++ b/packages/orchestration/test/exos/make-test-loa-kit.ts @@ -1,9 +1,9 @@ +/* eslint-disable jsdoc/require-param -- ts types */ import { heapVowE as E } from '@agoric/vow/vat.js'; import { prepareRecorderKitMakers } from '@agoric/zoe/src/contractSupport/recorder.js'; import { Far } from '@endo/far'; import { ExecutionContext } from 'ava'; import { prepareLocalOrchestrationAccountKit } from '../../src/exos/local-orchestration-account.js'; -import { makeChainHub } from '../../src/exos/chain-hub.js'; import { commonSetup } from '../supports.js'; /** @@ -13,19 +13,17 @@ import { commonSetup } from '../supports.js'; * * Helps reduce boilerplate in test files, and retains testing context through * parameterized endowments. - * - * @param t - * @param bootstrap - * @param opts - * @param opts.zcf */ export const prepareMakeTestLOAKit = ( t: ExecutionContext, - bootstrap: Awaited>['bootstrap'], + { + bootstrap, + facadeServices: { chainHub }, + utils, + }: Awaited>, { zcf = Far('MockZCF', {}) } = {}, ) => { - const { timer, localchain, marshaller, rootZone, vowTools, agoricNames } = - bootstrap; + const { timer, localchain, marshaller, rootZone, vowTools } = bootstrap; const { makeRecorderKit } = prepareRecorderKitMakers( rootZone.mapStore('recorder'), @@ -39,7 +37,7 @@ export const prepareMakeTestLOAKit = ( zcf, timer, vowTools, - makeChainHub(agoricNames, vowTools), + chainHub, ); return async ({ @@ -61,6 +59,9 @@ 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 5c215731212..53255f27610 100644 --- a/packages/orchestration/test/exos/portfolio-holder-kit.test.ts +++ b/packages/orchestration/test/exos/portfolio-holder-kit.test.ts @@ -7,8 +7,8 @@ import { prepareMakeTestLOAKit } from './make-test-loa-kit.js'; import { prepareMakeTestCOAKit } from './make-test-coa-kit.js'; test('portfolio holder kit behaviors', async t => { - const { bootstrap } = await commonSetup(t); - const { rootZone, storage, vowTools } = bootstrap; + const common = await commonSetup(t); + const { rootZone, storage, vowTools } = common.bootstrap; const storageNode = storage.rootNode.makeChildNode('accounts'); /** @@ -23,8 +23,10 @@ test('portfolio holder kit behaviors', async t => { }, }); - const makeTestCOAKit = prepareMakeTestCOAKit(t, bootstrap, { zcf: mockZcf }); - const makeTestLOAKit = prepareMakeTestLOAKit(t, bootstrap, { zcf: mockZcf }); + const makeTestCOAKit = prepareMakeTestCOAKit(t, common.bootstrap, { + zcf: mockZcf, + }); + const makeTestLOAKit = prepareMakeTestLOAKit(t, common, { zcf: mockZcf }); const makeCosmosAccount = async ({ chainId, hostConnectionId, diff --git a/packages/orchestration/test/supports.ts b/packages/orchestration/test/supports.ts index cbc58d3cf21..7e81bdfa747 100644 --- a/packages/orchestration/test/supports.ts +++ b/packages/orchestration/test/supports.ts @@ -26,6 +26,8 @@ import { registerKnownChains } from '../src/chain-info.js'; import { prepareCosmosInterchainService } from '../src/exos/cosmos-interchain-service.js'; import { setupFakeNetwork } from './network-fakes.js'; import { buildVTransferEvent } from '../tools/ibc-mocks.js'; +import { makeChainHub } from '../src/exos/chain-hub.js'; +import fetchedChainInfo from '../src/fetched-chain-info.js'; export { makeFakeLocalchainBridge, @@ -147,6 +149,25 @@ export const commonSetup = async (t: ExecutionContext) => { await eventLoopIteration(); }; + const chainHub = makeChainHub(agoricNames, vowTools); + + /** + * Register BLD if it's not already registered + */ + const registerAgoricBld = () => { + try { + chainHub.lookupAsset('ubld'); + } catch { + chainHub.registerChain('agoric', fetchedChainInfo.agoric); + chainHub.registerAsset('ubld', { + chainName: 'agoric', + baseName: 'agoric', + baseDenom: 'ubld', + brand: bld.brand, + }); + } + }; + return { bootstrap: { agoricNames, @@ -154,11 +175,13 @@ export const commonSetup = async (t: ExecutionContext) => { bankManager, timer, localchain, + // TODO remove; bootstrap doesn't havemarshaller marshaller, cosmosInterchainService, // TODO remove; bootstrap doesn't have a zone rootZone: rootZone.subZone('contract'), storage, + // TODO remove; bootstrap doesn't have vowTools vowTools, }, brands: { @@ -179,6 +202,7 @@ export const commonSetup = async (t: ExecutionContext) => { }, facadeServices: { agoricNames, + chainHub, localchain, orchestrationService: cosmosInterchainService, timerService: timer, @@ -187,6 +211,7 @@ export const commonSetup = async (t: ExecutionContext) => { pourPayment, inspectLocalBridge: () => harden([...localBridgeMessages]), inspectDibcBridge: () => E(ibcBridge).inspectDibcBridge(), + registerAgoricBld, transmitTransferAck, }, }; From ec676b45cd115212c4ab61dc7947270b8b358db1 Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Thu, 29 Aug 2024 10:32:44 -0700 Subject: [PATCH 12/14] test: COA transfer --- .../exos/cosmos-orchestration-account.test.ts | 45 ++++++++++++------- .../test/exos/make-test-coa-kit.ts | 28 +++++------- .../test/exos/portfolio-holder-kit.test.ts | 2 +- 3 files changed, 41 insertions(+), 34 deletions(-) diff --git a/packages/orchestration/test/exos/cosmos-orchestration-account.test.ts b/packages/orchestration/test/exos/cosmos-orchestration-account.test.ts index 21fd111991b..02f6d32066f 100644 --- a/packages/orchestration/test/exos/cosmos-orchestration-account.test.ts +++ b/packages/orchestration/test/exos/cosmos-orchestration-account.test.ts @@ -16,6 +16,7 @@ import { buildTxPacketString, parseOutgoingTxPacket, } from '../../tools/ibc-mocks.js'; +import { defaultMockAckMap } from '../ibc-mocks.js'; type TestContext = Awaited>; @@ -27,11 +28,11 @@ test.beforeEach(async t => { test('send (to addr on same chain)', async t => { const { - bootstrap, brands: { ist }, + facadeServices: { chainHub }, utils: { inspectDibcBridge }, } = t.context; - const makeTestCOAKit = prepareMakeTestCOAKit(t, bootstrap); + const makeTestCOAKit = prepareMakeTestCOAKit(t, t.context); const account = await makeTestCOAKit(); t.assert(account, 'account is returned'); @@ -46,7 +47,7 @@ test('send (to addr on same chain)', async t => { await E(account).send(toAddress, { value: 10n, denom: 'uatom', - } as AmountArg), + }), undefined, ); @@ -57,12 +58,10 @@ test('send (to addr on same chain)', async t => { { message: 'ABCI code: 5: error handling packet: see events for details' }, ); - // ertp amounts not supported - await t.throwsAsync( - E(account).send(toAddress, ist.make(10n) as AmountArg), - // TODO #9211 register asset before sending - { message: 'No denomination for brand [object Alleged: IST brand]' }, - ); + // IST not registered + await t.throwsAsync(E(account).send(toAddress, ist.make(10n) as AmountArg), { + message: 'No denomination for brand [object Alleged: IST brand]', + }); // multi-send (sendAll) t.is( @@ -85,7 +84,7 @@ test('send (to addr on same chain)', async t => { test('transfer', async t => { const { brands: { ist }, - bootstrap, + facadeServices: { chainHub }, utils: { inspectDibcBridge }, mocks: { ibcBridge }, } = t.context; @@ -139,12 +138,22 @@ test('transfer', async t => { const transferResp = buildMsgResponseString(MsgTransferResponse, { sequence: 0n, }); + + const uistTransfer = toTransferTxPacket({ + ...mockIbcTransfer, + token: { + denom: 'uist', + amount: '10', + }, + }); + return { [defaultTransfer]: transferResp, [customTimeoutHeight]: transferResp, [customTimeoutTimestamp]: transferResp, [customTimeout]: transferResp, [customMemo]: transferResp, + [uistTransfer]: transferResp, }; }; ibcBridge.setMockAck(buildMocks()); @@ -160,7 +169,7 @@ test('transfer', async t => { }; t.log('Make account on cosmoshub'); - const makeTestCOAKit = prepareMakeTestCOAKit(t, bootstrap); + const makeTestCOAKit = prepareMakeTestCOAKit(t, t.context); const account = await makeTestCOAKit(); t.log('Send tokens from cosmoshub to noble'); @@ -258,11 +267,18 @@ test('transfer', async t => { }, ); - t.log("transfer doesn't support ERTP brands yet. see #9211"); + t.log('transfer throws if asset is not in its chainHub'); await t.throwsAsync(E(account).transfer(ist.make(10n), mockDestination), { - // TODO #9211 register asset before transfer message: 'No denomination for brand [object Alleged: IST brand]', }); + chainHub.registerAsset('uist', { + baseDenom: 'uist', + baseName: 'agoric', + brand: ist.brand, + chainName: 'agoric', + }); + // uses uistTransfer mock above + await E(account).transfer(ist.make(10n), mockDestination); t.log('transfer timeout error recieved and handled from the bridge'); await t.throwsAsync( @@ -286,8 +302,7 @@ test('transfer', async t => { }); test('not yet implemented', async t => { - const { bootstrap } = await commonSetup(t); - const makeTestCOAKit = prepareMakeTestCOAKit(t, bootstrap); + const makeTestCOAKit = prepareMakeTestCOAKit(t, t.context); const account = await makeTestCOAKit(); const mockAmountArg: AmountArg = { value: 10n, denom: 'uatom' }; diff --git a/packages/orchestration/test/exos/make-test-coa-kit.ts b/packages/orchestration/test/exos/make-test-coa-kit.ts index 636ac23baaf..5817490ca8d 100644 --- a/packages/orchestration/test/exos/make-test-coa-kit.ts +++ b/packages/orchestration/test/exos/make-test-coa-kit.ts @@ -1,10 +1,10 @@ -import { Far } from '@endo/far'; +/* eslint-disable jsdoc/require-param -- ts types */ import { heapVowE as E } from '@agoric/vow/vat.js'; import { prepareRecorderKitMakers } from '@agoric/zoe/src/contractSupport/recorder.js'; +import { Far } from '@endo/far'; import type { ExecutionContext } from 'ava'; -import { commonSetup } from '../supports.js'; import { prepareCosmosOrchestrationAccount } from '../../src/exos/cosmos-orchestration-account.js'; -import { makeChainHub } from '../../src/exos/chain-hub.js'; +import { commonSetup } from '../supports.js'; /** * A testing utility that creates a (Cosmos)ChainAccount and makes a @@ -13,25 +13,14 @@ import { makeChainHub } from '../../src/exos/chain-hub.js'; * * Helps reduce boilerplate in test files, and retains testing context through * parameterized endowments. - * - * @param t - * @param bootstrap - * @param opts - * @param opts.zcf */ export const prepareMakeTestCOAKit = ( t: ExecutionContext, - bootstrap: Awaited>['bootstrap'], + { bootstrap, facadeServices, utils }: Awaited>, { zcf = Far('MockZCF', {}) } = {}, ) => { - const { - cosmosInterchainService, - marshaller, - rootZone, - timer, - vowTools, - agoricNames, - } = bootstrap; + const { cosmosInterchainService, marshaller, rootZone, timer, vowTools } = + bootstrap; const { makeRecorderKit } = prepareRecorderKitMakers( rootZone.mapStore('CosmosOrchAccountRecorder'), @@ -41,7 +30,7 @@ export const prepareMakeTestCOAKit = ( const makeCosmosOrchestrationAccount = prepareCosmosOrchestrationAccount( rootZone.subZone('CosmosOrchAccount'), { - chainHub: makeChainHub(agoricNames, vowTools), + chainHub: facadeServices.chainHub, makeRecorderKit, timerService: timer, vowTools, @@ -83,6 +72,9 @@ export const prepareMakeTestCOAKit = ( }, ); + t.log('register Agoric chain and BLD in ChainHub'); + utils.registerAgoricBld(); + return holder; }; }; diff --git a/packages/orchestration/test/exos/portfolio-holder-kit.test.ts b/packages/orchestration/test/exos/portfolio-holder-kit.test.ts index 53255f27610..5151873f908 100644 --- a/packages/orchestration/test/exos/portfolio-holder-kit.test.ts +++ b/packages/orchestration/test/exos/portfolio-holder-kit.test.ts @@ -23,7 +23,7 @@ test('portfolio holder kit behaviors', async t => { }, }); - const makeTestCOAKit = prepareMakeTestCOAKit(t, common.bootstrap, { + const makeTestCOAKit = prepareMakeTestCOAKit(t, common, { zcf: mockZcf, }); const makeTestLOAKit = prepareMakeTestLOAKit(t, common, { zcf: mockZcf }); From 4a07907b9cb455170b9c4d4686f0a3d3f7192df2 Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Thu, 29 Aug 2024 15:29:27 -0700 Subject: [PATCH 13/14] BREAKING CHANGE: getBrandInfo -> getDenomInfo --- packages/orchestration/src/exos/orchestrator.js | 12 ++++++------ packages/orchestration/src/orchestration-api.ts | 6 +++--- packages/orchestration/src/typeGuards.js | 6 +++--- packages/orchestration/test/exos/chain-hub.test.ts | 2 +- packages/orchestration/test/facade.test.ts | 8 ++++---- packages/orchestration/test/types.test-d.ts | 4 ++-- 6 files changed, 19 insertions(+), 19 deletions(-) diff --git a/packages/orchestration/src/exos/orchestrator.js b/packages/orchestration/src/exos/orchestrator.js index bc525e992a5..fb8adcc11e1 100644 --- a/packages/orchestration/src/exos/orchestrator.js +++ b/packages/orchestration/src/exos/orchestrator.js @@ -7,7 +7,7 @@ import { Fail, q } from '@endo/errors'; import { E } from '@endo/far'; import { M } from '@endo/patterns'; import { - BrandInfoShape, + DenomInfoShape, ChainInfoShape, DenomAmountShape, DenomShape, @@ -37,7 +37,7 @@ const trace = makeTracer('Orchestrator'); export const OrchestratorI = M.interface('Orchestrator', { getChain: M.call(M.string()).returns(Vow$(ChainInfoShape)), makeLocalAccount: M.call().returns(Vow$(LocalChainAccountShape)), - getBrandInfo: M.call(DenomShape).returns(BrandInfoShape), + getDenomInfo: M.call(DenomShape).returns(DenomInfoShape), asAmount: M.call(DenomAmountShape).returns(AmountShape), }); @@ -140,15 +140,15 @@ const prepareOrchestratorKit = ( makeLocalAccount() { return watch(E(localchain).makeAccount()); }, - /** @type {HostOf} */ - getBrandInfo(denom) { + /** @type {HostOf} */ + getDenomInfo(denom) { const { chainName, baseName, baseDenom, brand } = chainHub.lookupAsset(denom); chainByName.has(chainName) || - Fail`use getChain(${q(chainName)}) before getBrandInfo(${q(denom)})`; + Fail`use getChain(${q(chainName)}) before getDenomInfo(${q(denom)})`; const chain = chainByName.get(chainName); chainByName.has(baseName) || - Fail`use getChain(${q(baseName)}) before getBrandInfo(${q(denom)})`; + Fail`use getChain(${q(baseName)}) before getDenomInfo(${q(denom)})`; const base = chainByName.get(baseName); return harden({ chain, base, brand, baseDenom }); }, diff --git a/packages/orchestration/src/orchestration-api.ts b/packages/orchestration/src/orchestration-api.ts index b2b8856e930..9c065056d80 100644 --- a/packages/orchestration/src/orchestration-api.ts +++ b/packages/orchestration/src/orchestration-api.ts @@ -101,7 +101,7 @@ export interface Chain { // TODO provide a way to get the local denom/brand/whatever for this chain } -export interface BrandInfo< +export interface DenomInfo< HoldingChain extends keyof KnownChains, IssuingChain extends keyof KnownChains, > { @@ -133,12 +133,12 @@ export interface Orchestrator { * issues the corresponding asset. * @param denom */ - getBrandInfo: < + getDenomInfo: < HoldingChain extends keyof KnownChains, IssuingChain extends keyof KnownChains, >( denom: Denom, - ) => BrandInfo; + ) => DenomInfo; // TODO preload the mapping so this can be synchronous /** * Convert an amount described in native data to a local, structured Amount. diff --git a/packages/orchestration/src/typeGuards.js b/packages/orchestration/src/typeGuards.js index 2d27c39cd95..f1c9ccea31b 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, DenomDetail, BrandInfo} from './types.js'; + * @import {ChainAddress, CosmosAssetInfo, Chain, ChainInfo, CosmosChainInfo, DenomAmount, DenomDetail, DenomInfo} from './types.js'; * @import {Delegation} from '@agoric/cosmic-proto/cosmos/staking/v1beta1/staking.js'; * @import {TxBody} from '@agoric/cosmic-proto/cosmos/tx/v1beta1/tx.js'; * @import {TypedJson} from '@agoric/cosmic-proto'; @@ -112,8 +112,8 @@ export const ChainInfoShape = M.splitRecord({ export const LocalChainAccountShape = M.remotable('LocalChainAccount'); export const DenomShape = M.string(); -/** @type {TypedPattern>} */ -export const BrandInfoShape = { +/** @type {TypedPattern>} */ +export const DenomInfoShape = { chain: M.remotable('Chain'), base: M.remotable('Chain'), brand: M.or(M.remotable('Brand'), M.undefined()), diff --git a/packages/orchestration/test/exos/chain-hub.test.ts b/packages/orchestration/test/exos/chain-hub.test.ts index e5cb3c05c07..4453c14ba34 100644 --- a/packages/orchestration/test/exos/chain-hub.test.ts +++ b/packages/orchestration/test/exos/chain-hub.test.ts @@ -95,7 +95,7 @@ test.serial('getConnectionInfo', async t => { t.deepEqual(await vt.when(chainHub.getConnectionInfo(b, a)), ba); }); -test('getBrandInfo support', async t => { +test('getDenomInfo support', async t => { const { chainHub } = setup(); const denom = 'utok1'; diff --git a/packages/orchestration/test/facade.test.ts b/packages/orchestration/test/facade.test.ts index 63eaf8fc681..99c685433a3 100644 --- a/packages/orchestration/test/facade.test.ts +++ b/packages/orchestration/test/facade.test.ts @@ -185,7 +185,7 @@ test.serial('asset / denom info', async t => { const c1 = await orc.getChain(mockChainInfo.chainId); { - const actual = orc.getBrandInfo('utoken1'); + const actual = orc.getDenomInfo('utoken1'); console.log('actual', actual); const info = await actual.chain.getChainInfo(); t.deepEqual(info, mockChainInfo); @@ -200,7 +200,7 @@ test.serial('asset / denom info', async t => { const ag = await orc.getChain('agoric'); { - const actual = orc.getBrandInfo(agDenom); + const actual = orc.getDenomInfo(agDenom); t.deepEqual(actual, { chain: ag, @@ -223,11 +223,11 @@ test.serial('asset / denom info', async t => { }); const missingGetChain = orchestrate('missing getChain', {}, async orc => { - const actual = orc.getBrandInfo('utoken2'); + const actual = orc.getDenomInfo('utoken2'); }); await t.throwsAsync(vt.when(missingGetChain()), { - message: 'use getChain("anotherChain") before getBrandInfo("utoken2")', + message: 'use getChain("anotherChain") before getDenomInfo("utoken2")', }); }); diff --git a/packages/orchestration/test/types.test-d.ts b/packages/orchestration/test/types.test-d.ts index d6d0c539da2..6a588bfe80b 100644 --- a/packages/orchestration/test/types.test-d.ts +++ b/packages/orchestration/test/types.test-d.ts @@ -101,8 +101,8 @@ expectNotType(chainAddr); // Negative test expectNotType<() => Promise>(vowFn); - const getBrandInfo: HostOf = null as any; - const chainHostOf = getBrandInfo('uatom').chain; + const getDenomInfo: HostOf = null as any; + const chainHostOf = getDenomInfo('uatom').chain; expectType>(chainHostOf.getChainInfo()); } From 4cac5cca83da2d9494b90551d71218de76b0af71 Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Thu, 29 Aug 2024 16:41:00 -0700 Subject: [PATCH 14/14] test: increase max-retries --- multichain-testing/test/auto-stake-it.test.ts | 4 ++- multichain-testing/test/config.ts | 32 +++++++++++++++++++ multichain-testing/test/stake-ica.test.ts | 20 ++---------- multichain-testing/tools/ibc-transfer.ts | 6 ++-- 4 files changed, 41 insertions(+), 21 deletions(-) create mode 100644 multichain-testing/test/config.ts diff --git a/multichain-testing/test/auto-stake-it.test.ts b/multichain-testing/test/auto-stake-it.test.ts index 16e32bdf9a4..5aee9d6fa15 100644 --- a/multichain-testing/test/auto-stake-it.test.ts +++ b/multichain-testing/test/auto-stake-it.test.ts @@ -11,6 +11,7 @@ import { import { makeQueryClient } from '../tools/query.js'; import type { SetupContextWithWallets } from './support.js'; import { chainConfig, commonSetup } from './support.js'; +import { AUTO_STAKE_IT_DELEGATIONS_TIMEOUT } from './config.js'; const test = anyTest as TestFn; @@ -179,7 +180,8 @@ const autoStakeItScenario = test.macro({ const { delegation_responses } = await retryUntilCondition( () => remoteQueryClient.queryDelegations(icaAddress), ({ delegation_responses }) => !!delegation_responses.length, - `delegations visible on ${chainName}`, + `auto-stake-it delegations visible on ${chainName}`, + AUTO_STAKE_IT_DELEGATIONS_TIMEOUT, ); t.log('delegation balance', delegation_responses[0]?.balance); t.like( diff --git a/multichain-testing/test/config.ts b/multichain-testing/test/config.ts new file mode 100644 index 00000000000..a8619398f73 --- /dev/null +++ b/multichain-testing/test/config.ts @@ -0,0 +1,32 @@ +import type { RetryOptions } from '../tools/sleep.js'; + +/** + * Wait 90 seconds to ensure staking rewards are available. + * + * While we expect staking rewards to be available after a + * single block (~5-12 seconds for most chains), this provides additional + * padding after observed failures in CI + * (https://github.com/Agoric/agoric-sdk/issues/9934). + * + * A more robust approach might consider Distribution params and the + * {@link FAUCET_POUR} constant to determine how many blocks it should take for + * rewards to be available. + */ +export const STAKING_REWARDS_TIMEOUT: RetryOptions = { + retryIntervalMs: 5000, + maxRetries: 18, +}; + +/** + * Wait 2 minutes to ensure: + * - IBC Transfer from LocalAccount -> ICA Account Completes + * - Delegation from ICA Account (initiated from SwingSet) Completes + * - Delegations are visible via LCD (API Endpoint) + * + * Most of the time this finishes in <7 seconds, but other times it + * appears to take much longer. + */ +export const AUTO_STAKE_IT_DELEGATIONS_TIMEOUT: RetryOptions = { + retryIntervalMs: 5000, + maxRetries: 24, +}; diff --git a/multichain-testing/test/stake-ica.test.ts b/multichain-testing/test/stake-ica.test.ts index 01cbeabcc7d..d3007496450 100644 --- a/multichain-testing/test/stake-ica.test.ts +++ b/multichain-testing/test/stake-ica.test.ts @@ -7,29 +7,13 @@ import { } from './support.js'; import { makeDoOffer } from '../tools/e2e-tools.js'; import { makeQueryClient } from '../tools/query.js'; -import { sleep, type RetryOptions } from '../tools/sleep.js'; +import { sleep } from '../tools/sleep.js'; +import { STAKING_REWARDS_TIMEOUT } from './config.js'; const test = anyTest as TestFn; const accounts = ['user1', 'user2']; -/** - * Wait 90 seconds to ensure staking rewards are available. - * - * While we expect staking rewards to be available after a - * single block (~5-12 seconds for most chains), this provide additional - * padding after observed failures in CI - * (https://github.com/Agoric/agoric-sdk/issues/9934). - * - * A more robust approach might consider Distribution params and the - * {@link FAUCET_POUR} constant to determine how many blocks it should take for - * rewards to be available. - */ -export const STAKING_REWARDS_TIMEOUT: RetryOptions = { - retryIntervalMs: 5000, - maxRetries: 18, -}; - test.before(async t => { const { deleteTestKeys, setupTestKeys, ...rest } = await commonSetup(t); // XXX not necessary for CI, but helpful for unexpected failures in diff --git a/multichain-testing/tools/ibc-transfer.ts b/multichain-testing/tools/ibc-transfer.ts index 173f8c9aaa6..7d4fdae8624 100644 --- a/multichain-testing/tools/ibc-transfer.ts +++ b/multichain-testing/tools/ibc-transfer.ts @@ -37,12 +37,14 @@ type SimpleChainAddress = { chainName: string; }; -export const DEFAULT_TIMEOUT_NS = 1893456000000000000n; +// 2030-01-01T00:00:00Z +export const DEFAULT_TIMEOUT_NS = + 1893456000n * NANOSECONDS_PER_MILLISECOND * MILLISECONDS_PER_SECOND; /** * @param {number} [ms] current time in ms (e.g. Date.now()) * @param {bigint} [minutes=5n] number of minutes in the future - * @returns {bigint} nanosecond timestamp 5 mins in the future */ + * @returns {bigint} nanosecond timestamp absolute since Unix epoch */ export const getTimeout = (ms: number = 0, minutes = 5n) => { // UNTIL #9200. timestamps are getting clobbered somewhere along the way // and we are observing failed transfers with timeouts years in the past.