diff --git a/multichain-testing/test/auto-stake-it.test.ts b/multichain-testing/test/auto-stake-it.test.ts index 69af5caf669..5aee9d6fa15 100644 --- a/multichain-testing/test/auto-stake-it.test.ts +++ b/multichain-testing/test/auto-stake-it.test.ts @@ -1,16 +1,17 @@ +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'; +import { AUTO_STAKE_IT_DELEGATIONS_TIMEOUT } from './config.js'; const test = anyTest as TestFn; @@ -82,6 +83,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 +93,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 +139,6 @@ const autoStakeItScenario = test.macro({ encoding: 'bech32', chainId: remoteChainInfo.chainId, }, - localDenom: `ibc/${hash}`, }, proposal: {}, }); @@ -203,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. 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/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/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/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; diff --git a/packages/orchestration/src/exos/chain-hub.js b/packages/orchestration/src/exos/chain-hub.js index ea47e5f2f67..e9e9a53259a 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} */ @@ -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; diff --git a/packages/orchestration/src/exos/cosmos-orchestration-account.js b/packages/orchestration/src/exos/cosmos-orchestration-account.js index 8af61ed745d..47d4f792d45 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 } from '@agoric/internal'; import { Shape as NetworkShape } from '@agoric/network'; @@ -31,6 +31,7 @@ import { DenomAmountShape, IBCTransferOptionsShape, } from '../typeGuards.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'; @@ -242,15 +243,7 @@ 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.`; - } - return harden({ - denom: amount.denom, - amount: String(amount.value), - }); + return coerceCoin(chainHub, amount); }, }, balanceQueryWatcher: { @@ -574,14 +567,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/exos/local-orchestration-account.js b/packages/orchestration/src/exos/local-orchestration-account.js index 5bfe93eb92c..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: { @@ -392,12 +385,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), @@ -435,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', { @@ -460,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([ @@ -555,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( @@ -578,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/src/exos/orchestrator.js b/packages/orchestration/src/exos/orchestrator.js index 6aad87ef1ea..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, @@ -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'; @@ -38,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), }); @@ -141,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 6fa1be523fe..9c065056d80 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 = { @@ -101,6 +101,20 @@ export interface Chain { // TODO provide a way to get the local denom/brand/whatever for this chain } +export interface DenomInfo< + 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()`. */ @@ -119,21 +133,12 @@ export interface Orchestrator { * issues the corresponding asset. * @param denom */ - getBrandInfo: < + getDenomInfo: < HoldingChain extends keyof KnownChains, 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; - }; + ) => 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 fad82ee03e9..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, ChainInfo, CosmosChainInfo, DenomAmount} 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'; @@ -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 DenomInfoShape = { + 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/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), + }); +}; 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/auto-stake-it.contract.test.ts b/packages/orchestration/test/examples/auto-stake-it.contract.test.ts index 0401d00adb6..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 }, @@ -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(); 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(); 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/exos/cosmos-orchestration-account.test.ts b/packages/orchestration/test/exos/cosmos-orchestration-account.test.ts index 497976a6f33..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>; @@ -25,13 +26,13 @@ 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 }, + 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('CosmosOrchestrationAccount - 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('CosmosOrchestrationAccount - 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 lookup denom from brand - { message: 'Brands not currently supported.' }, - ); + // 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( @@ -82,10 +81,10 @@ test('CosmosOrchestrationAccount - send (to addr on same chain)', async t => { ); }); -test('CosmosOrchestrationAccount - transfer', async t => { +test('transfer', async t => { const { brands: { ist }, - bootstrap, + facadeServices: { chainHub }, utils: { inspectDibcBridge }, mocks: { ibcBridge }, } = t.context; @@ -139,12 +138,22 @@ test('CosmosOrchestrationAccount - 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('CosmosOrchestrationAccount - 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,10 +267,18 @@ test('CosmosOrchestrationAccount - 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), { - message: 'Brands not currently supported.', + 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( @@ -284,9 +301,8 @@ test('CosmosOrchestrationAccount - transfer', async t => { ); }); -test('CosmosOrchestrationAccount - not yet implemented', async t => { - const { bootstrap } = await commonSetup(t); - const makeTestCOAKit = prepareMakeTestCOAKit(t, bootstrap); +test('not yet implemented', async t => { + const makeTestCOAKit = prepareMakeTestCOAKit(t, t.context); const account = await makeTestCOAKit(); const mockAmountArg: AmountArg = { value: 10n, denom: 'uatom' }; 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-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/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..5151873f908 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, { + zcf: mockZcf, + }); + const makeTestLOAKit = prepareMakeTestLOAKit(t, common, { zcf: mockZcf }); const makeCosmosAccount = async ({ chainId, hostConnectionId, diff --git a/packages/orchestration/test/facade.test.ts b/packages/orchestration/test/facade.test.ts index 5655d80895f..99c685433a3 100644 --- a/packages/orchestration/test/facade.test.ts +++ b/packages/orchestration/test/facade.test.ts @@ -185,7 +185,8 @@ 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); @@ -199,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, @@ -222,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/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/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/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 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, }, }; 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()); } 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 {