diff --git a/packages/agoric-cli/package.json b/packages/agoric-cli/package.json index 6b7db016abb..cbe25b768fa 100644 --- a/packages/agoric-cli/package.json +++ b/packages/agoric-cli/package.json @@ -99,6 +99,6 @@ "workerThreads": false }, "typeCoverage": { - "atLeast": 77.3 + "atLeast": 77.36 } } diff --git a/packages/async-flow/package.json b/packages/async-flow/package.json index e1462adada2..00cacb143c3 100644 --- a/packages/async-flow/package.json +++ b/packages/async-flow/package.json @@ -61,6 +61,6 @@ "workerThreads": false }, "typeCoverage": { - "atLeast": 76.95 + "atLeast": 76.94 } } diff --git a/packages/async-flow/src/async-flow.js b/packages/async-flow/src/async-flow.js index 7993531e37d..654c12d209f 100644 --- a/packages/async-flow/src/async-flow.js +++ b/packages/async-flow/src/async-flow.js @@ -13,7 +13,7 @@ import { LogEntryShape, FlowStateShape } from './type-guards.js'; /** * @import {WeakMapStore} from '@agoric/store' * @import {Zone} from '@agoric/base-zone' - * @import {FlowState, GuestAsyncFunc, HostAsyncFuncWrapper, PreparationOptions} from '../src/types.js' + * @import {FlowState, GuestAsyncFunc, HostAsyncFuncWrapper, HostOf, PreparationOptions} from '../src/types.js' * @import {ReplayMembrane} from '../src/replay-membrane.js' */ @@ -434,21 +434,25 @@ export const prepareAsyncFlowTools = (outerZone, outerOptions = {}) => { }; /** + * @template {GuestAsyncFunc} F * @param {Zone} zone * @param {string} tag - * @param {GuestAsyncFunc} guestFunc + * @param {F} guestFunc * @param {{ startEager?: boolean }} [options] - * @returns {HostAsyncFuncWrapper} + * @returns {HostOf} */ const asyncFlow = (zone, tag, guestFunc, options = undefined) => { const makeAsyncFlowKit = prepareAsyncFlowKit(zone, tag, guestFunc, options); const hostFuncName = `${tag}_hostFlow`; - const wrapperFunc = { - [hostFuncName](...args) { - const { flow } = makeAsyncFlowKit(args); - return flow.getOutcome(); - }, - }[hostFuncName]; + + const wrapperFunc = /** @type {HostOf} */ ( + { + [hostFuncName](...args) { + const { flow } = makeAsyncFlowKit(args); + return flow.getOutcome(); + }, + }[hostFuncName] + ); defineProperties(wrapperFunc, { length: { value: guestFunc.length }, }); diff --git a/packages/async-flow/src/types.d.ts b/packages/async-flow/src/types.d.ts index 95b2b6f6a6e..77ec495194e 100644 --- a/packages/async-flow/src/types.d.ts +++ b/packages/async-flow/src/types.d.ts @@ -51,6 +51,13 @@ type HostInterface = { [K in keyof T]: HostOf; }; +/** + * Convert an entire Host interface into what the Guest will receive. + */ +type GuestInterface = { + [K in keyof T]: GuestOf; +}; + /** * The function the host must provide to match an interface the guest expects. * @@ -60,6 +67,8 @@ export type HostOf = F extends (...args: infer A) => Promise ? (...args: A) => Vow> : F; +export type HostArgs = { [K in keyof GA]: HostOf }; + export type PreparationOptions = { vowTools?: VowTools; makeLogStore?: (() => LogStore) | undefined; diff --git a/packages/boot/package.json b/packages/boot/package.json index 35cb46ea959..076b4254f77 100644 --- a/packages/boot/package.json +++ b/packages/boot/package.json @@ -90,6 +90,6 @@ "workerThreads": false }, "typeCoverage": { - "atLeast": 86.66 + "atLeast": 86.64 } } diff --git a/packages/internal/package.json b/packages/internal/package.json index f6f71938a38..9e94cf3ff18 100755 --- a/packages/internal/package.json +++ b/packages/internal/package.json @@ -57,6 +57,6 @@ "access": "public" }, "typeCoverage": { - "atLeast": 93.78 + "atLeast": 93.82 } } diff --git a/packages/orchestration/package.json b/packages/orchestration/package.json index 65be9dfcfd0..cd307b5e38e 100644 --- a/packages/orchestration/package.json +++ b/packages/orchestration/package.json @@ -92,6 +92,6 @@ "access": "public" }, "typeCoverage": { - "atLeast": 97.99 + "atLeast": 98.04 } } diff --git a/packages/orchestration/src/examples/basic-flows.contract.js b/packages/orchestration/src/examples/basic-flows.contract.js index 1771f9e3f88..d3b8a60d71e 100644 --- a/packages/orchestration/src/examples/basic-flows.contract.js +++ b/packages/orchestration/src/examples/basic-flows.contract.js @@ -9,6 +9,7 @@ import { provideOrchestration } from '../utils/start-helper.js'; /** * @import {Baggage} from '@agoric/vat-data'; * @import {Orchestrator} from '@agoric/orchestration'; + * @import {Vow, VowTools} from '@agoric/vow'; * @import {OrchestrationPowers} from '../utils/start-helper.js'; */ @@ -44,7 +45,6 @@ export const start = async (zcf, privateArgs, baggage) => { privateArgs.marshaller, ); - /** @type {OfferHandler} */ const makeOrchAccount = orchestrate( 'makeOrchAccount', undefined, diff --git a/packages/orchestration/src/examples/swapExample.contract.js b/packages/orchestration/src/examples/swapExample.contract.js index a7d4bb806b1..1415d3b23e9 100644 --- a/packages/orchestration/src/examples/swapExample.contract.js +++ b/packages/orchestration/src/examples/swapExample.contract.js @@ -5,7 +5,6 @@ import { orcUtils } from '../utils/orc.js'; import { withOrchestration } from '../utils/start-helper.js'; /** - * @import {GuestInterface, GuestOf} from '@agoric/async-flow'; * @import {LocalTransfer} from '../utils/zoe-tools.js'; * @import {Orchestrator, CosmosValidatorAddress} from '../types.js' * @import {TimerService} from '@agoric/time'; @@ -102,12 +101,6 @@ const contract = async (zcf, privateArgs, zone, { orchestrate, zoeTools }) => { const { brands } = zcf.getTerms(); /** deprecated historical example */ - /** - * @type {OfferHandler< - * unknown, - * { staked: Amount<'nat'>; validator: CosmosValidatorAddress } - * >} - */ const swapAndStakeHandler = orchestrate( 'LSTTia', { zcf, localTransfer: zoeTools.localTransfer }, diff --git a/packages/orchestration/src/examples/unbondExample.contract.js b/packages/orchestration/src/examples/unbondExample.contract.js index 0b0beac9bf3..17efa3af2be 100644 --- a/packages/orchestration/src/examples/unbondExample.contract.js +++ b/packages/orchestration/src/examples/unbondExample.contract.js @@ -4,7 +4,6 @@ import { withOrchestration } from '../utils/start-helper.js'; /** * @import {Orchestrator, IcaAccount, CosmosValidatorAddress} from '../types.js' * @import {TimerService} from '@agoric/time'; - * @import {Baggage} from '@agoric/vat-data'; * @import {LocalChain} from '@agoric/vats/src/localchain.js'; * @import {NameHub} from '@agoric/vats'; * @import {Remote} from '@agoric/internal'; @@ -59,7 +58,6 @@ const unbondAndLiquidStakeFn = async (orch, { zcf }, _seat, _offerArgs) => { * @param {OrchestrationTools} tools */ const contract = async (zcf, privateArgs, zone, { orchestrate }) => { - /** @type {OfferHandler} */ const unbondAndLiquidStake = orchestrate( 'LSTTia', { zcf }, diff --git a/packages/orchestration/src/exos/cosmos-orchestration-account.js b/packages/orchestration/src/exos/cosmos-orchestration-account.js index f3d299b0cdb..bc86158ba54 100644 --- a/packages/orchestration/src/exos/cosmos-orchestration-account.js +++ b/packages/orchestration/src/exos/cosmos-orchestration-account.js @@ -329,7 +329,6 @@ export const prepareCosmosOrchestrationAccountKit = ( // expect this complete in the same run publicSubscribers: await when(holder.getPublicTopics()), invitationMakers, - holder, }); }); }, diff --git a/packages/orchestration/src/exos/local-chain-facade.js b/packages/orchestration/src/exos/local-chain-facade.js index 8770dbb741a..f00eb5e418e 100644 --- a/packages/orchestration/src/exos/local-chain-facade.js +++ b/packages/orchestration/src/exos/local-chain-facade.js @@ -134,3 +134,4 @@ export const prepareLocalChainFacade = (zone, powers) => { harden(prepareLocalChainFacade); /** @typedef {ReturnType} MakeLocalChainFacade */ +/** @typedef {ReturnType} LocalChainFacade */ diff --git a/packages/orchestration/src/exos/local-orchestration-account.js b/packages/orchestration/src/exos/local-orchestration-account.js index c5d168189be..4159912c91d 100644 --- a/packages/orchestration/src/exos/local-orchestration-account.js +++ b/packages/orchestration/src/exos/local-orchestration-account.js @@ -307,7 +307,6 @@ export const prepareLocalOrchestrationAccountKit = ( // expect this complete in the same run publicSubscribers: await when(holder.getPublicTopics()), invitationMakers, - holder, }); }); }, diff --git a/packages/orchestration/src/exos/remote-chain-facade.js b/packages/orchestration/src/exos/remote-chain-facade.js index a615fa2c84a..e33f14b6ae2 100644 --- a/packages/orchestration/src/exos/remote-chain-facade.js +++ b/packages/orchestration/src/exos/remote-chain-facade.js @@ -176,3 +176,4 @@ export const prepareRemoteChainFacade = (zone, powers) => { harden(prepareRemoteChainFacade); /** @typedef {ReturnType} MakeRemoteChainFacade */ +/** @typedef {ReturnType} RemoteChainFacade */ diff --git a/packages/orchestration/src/facade.js b/packages/orchestration/src/facade.js index ae6b4cf007a..709b8d26e65 100644 --- a/packages/orchestration/src/facade.js +++ b/packages/orchestration/src/facade.js @@ -3,7 +3,7 @@ import { assertAllDefined } from '@agoric/internal'; /** - * @import {AsyncFlowTools} from '@agoric/async-flow'; + * @import {AsyncFlowTools, GuestInterface, HostArgs} from '@agoric/async-flow'; * @import {Zone} from '@agoric/zone'; * @import {Vow, VowTools} from '@agoric/vow'; * @import {TimerService} from '@agoric/time'; @@ -52,27 +52,19 @@ export const makeOrchestrationFacade = ({ const { prepareEndowment, asyncFlow, adminAsyncFlow } = asyncFlowTools; - const { when } = vowTools; - /** - * @template GuestReturn - * @template HostReturn - * @template GuestContext - * @template HostContext - * @template {any[]} GuestArgs - * @template {any[]} HostArgs + * @template GR - return type + * @template HC - host context + * @template {any[]} GA - guest args * @param {string} durableName - the orchestration flow identity in the zone * (to resume across upgrades) - * @param {HostContext} hostCtx - values to pass through the async flow - * membrane + * @param {HC} hostCtx - values to pass through the async flow membrane * @param {( * guestOrc: Orchestrator, - * guestCtx: GuestContext, - * ...args: GuestArgs - * ) => Promise} guestFn - * @returns {(...args: HostArgs) => Promise} TODO returns a - * Promise for now for compat before use of asyncFlow. But really should be - * `Vow` + * guestCtx: GuestInterface, + * ...args: GA + * ) => Promise} guestFn + * @returns {(...args: HostArgs) => Vow} */ const orchestrate = (durableName, hostCtx, guestFn) => { const subZone = zone.subZone(durableName); @@ -86,10 +78,11 @@ export const makeOrchestrationFacade = ({ const hostFn = asyncFlow(subZone, 'asyncFlow', guestFn); - const orcFn = (...args) => - // TODO remove the `when` after fixing the return type - // to `Vow` - when(hostFn(wrappedOrc, wrappedCtx, ...args)); + // cast because return could be arbitrary subtype + const orcFn = /** @type {(...args: HostArgs) => Vow} */ ( + (...args) => hostFn(wrappedOrc, wrappedCtx, ...args) + ); + return harden(orcFn); }; diff --git a/packages/orchestration/src/typeGuards.js b/packages/orchestration/src/typeGuards.js index 13cc6a42752..056a677c397 100644 --- a/packages/orchestration/src/typeGuards.js +++ b/packages/orchestration/src/typeGuards.js @@ -4,7 +4,8 @@ import { M } from '@endo/patterns'; /** * @import {TypedPattern} from '@agoric/internal'; - * @import {ChainInfo, CosmosChainInfo} from './types.js'; + * @import {ChainAddress, ChainInfo, CosmosChainInfo, DenomAmount} from './types.js'; + * @import {Delegation} from '@agoric/cosmic-proto/cosmos/staking/v1beta1/staking.js'; */ /** @@ -22,6 +23,7 @@ export const OutboundConnectionHandlerI = M.interface( }, ); +/** @type {TypedPattern} */ export const ChainAddressShape = { chainId: M.string(), encoding: M.string(), @@ -33,12 +35,15 @@ export const Proto3Shape = { value: M.string(), }; +// XXX same as ChainAmountShape and DenomAmount type export const CoinShape = { value: M.bigint(), denom: M.string() }; export const ChainAmountShape = harden({ denom: M.string(), value: M.nat() }); export const AmountArgShape = M.or(AmountShape, ChainAmountShape); +// FIXME missing `delegatorAddress` from the type +/** @type {TypedPattern} */ export const DelegationShape = harden({ validatorAddress: M.string(), shares: M.string(), // TODO: bigint? @@ -103,6 +108,7 @@ export const DenomShape = M.string(); // TODO define for #9211 export const BrandInfoShape = M.any(); +/** @type {TypedPattern} */ export const DenomAmountShape = { denom: DenomShape, value: M.bigint() }; /** @see {Chain} */ diff --git a/packages/orchestration/test/examples/basic-flows.contract.test.ts b/packages/orchestration/test/examples/basic-flows.contract.test.ts index c95f4e7a117..a7b975c1cd1 100644 --- a/packages/orchestration/test/examples/basic-flows.contract.test.ts +++ b/packages/orchestration/test/examples/basic-flows.contract.test.ts @@ -61,19 +61,22 @@ const orchestrationAccountScenario = test.macro({ return t.fail(`Unknown chain: ${chainName}`); } - const { zoe, instance } = t.context; + const { + bootstrap: { vowTools: vt }, + zoe, + instance, + } = t.context; const publicFacet = await E(zoe).getPublicFacet(instance); const inv = E(publicFacet).makeOrchAccountInvitation(); const userSeat = E(zoe).offer(inv, {}, undefined, { chainName }); - // @ts-expect-error TODO: type expected offer result - const { holder, invitationMakers, publicSubscribers } = - await E(userSeat).getOfferResult(); + const { invitationMakers, publicSubscribers } = await vt.when( + E(userSeat).getOfferResult(), + ); - t.regex(getInterfaceOf(holder)!, /Orchestration (.*) holder/); t.regex(getInterfaceOf(invitationMakers)!, /invitationMakers/); const { description, storagePath, subscriber } = publicSubscribers.account; - t.regex(description, /Account holder/); + t.regex(description!, /Account holder/); const expectedStoragePath = `mockChainStorageRoot.basic-flows.${config.addressPrefix}`; t.is(storagePath, expectedStoragePath); diff --git a/packages/orchestration/test/examples/sendAnywhere.test.ts b/packages/orchestration/test/examples/sendAnywhere.test.ts index 247d4e1d607..e7e9221805c 100644 --- a/packages/orchestration/test/examples/sendAnywhere.test.ts +++ b/packages/orchestration/test/examples/sendAnywhere.test.ts @@ -60,6 +60,7 @@ test('send using arbitrary chain info', async t => { brands: { ist }, utils: { inspectLocalBridge, pourPayment }, } = await commonSetup(t); + const vt = bootstrap.vowTools; const { zoe, bundleAndInstall } = await setUpZoeForTest(); @@ -121,7 +122,7 @@ test('send using arbitrary chain info', async t => { { Send }, { destAddr: 'hot1destAddr', chainName }, ); - await E(userSeat).getOfferResult(); + await vt.when(E(userSeat).getOfferResult()); const history = inspectLocalBridge(); t.like(history, [ @@ -154,7 +155,7 @@ test('send using arbitrary chain info', async t => { { Send }, { destAddr: 'cosmos1destAddr', chainName: 'cosmoshub' }, ); - await E(userSeat).getOfferResult(); + await vt.when(E(userSeat).getOfferResult()); const history = inspectLocalBridge(); const { messages, address: execAddr } = history.at(-1); t.is(messages.length, 1); @@ -200,7 +201,7 @@ test('send using arbitrary chain info', async t => { { Send }, { destAddr: 'hot1destAddr', chainName: 'hot' }, ); - await E(userSeat).getOfferResult(); + await vt.when(E(userSeat).getOfferResult()); const history = inspectLocalBridge(); const { messages, address: execAddr } = history.at(-1); t.is(messages.length, 1); diff --git a/packages/orchestration/test/examples/stake-bld.contract.test.ts b/packages/orchestration/test/examples/stake-bld.contract.test.ts index ec409824dc0..09300cb8775 100644 --- a/packages/orchestration/test/examples/stake-bld.contract.test.ts +++ b/packages/orchestration/test/examples/stake-bld.contract.test.ts @@ -142,7 +142,6 @@ test('makeAccountInvitationMaker', async t => { const userSeat = await E(zoe).offer(inv); const offerResult = await E(userSeat).getOfferResult(); - t.true('holder' in offerResult, 'received account holder'); t.truthy('invitationMakers' in offerResult, 'received continuing invitation'); t.like(offerResult.publicSubscribers, { account: { diff --git a/packages/orchestration/test/examples/swapExample.test.ts b/packages/orchestration/test/examples/swapExample.test.ts index 37de86dc079..f87ab716969 100644 --- a/packages/orchestration/test/examples/swapExample.test.ts +++ b/packages/orchestration/test/examples/swapExample.test.ts @@ -68,7 +68,8 @@ test.skip('start', async t => { } as const, }, ); - const result = await E(userSeat).getOfferResult(); + const vt = bootstrap.vowTools; + const result = await vt.when(E(userSeat).getOfferResult()); t.is(result, undefined); // bank purse now has the 10 IST diff --git a/packages/orchestration/test/examples/unbondExample.test.ts b/packages/orchestration/test/examples/unbondExample.test.ts index 8cda01ebb18..830353380f4 100644 --- a/packages/orchestration/test/examples/unbondExample.test.ts +++ b/packages/orchestration/test/examples/unbondExample.test.ts @@ -14,6 +14,7 @@ type StartFn = test('start', async t => { const { + bootstrap: { vowTools: vt }, brands: { ist }, commonPrivateArgs, } = await commonSetup(t); @@ -47,7 +48,7 @@ test('start', async t => { {}, { validator: 'agoric1valopsfufu' }, ); - const result = await E(userSeat).getOfferResult(); + const result = await vt.when(E(userSeat).getOfferResult()); t.is(result, undefined); const tree = inspectMapStore(contractBaggage); diff --git a/packages/orchestration/test/facade.test.ts b/packages/orchestration/test/facade.test.ts index 1e0e594f964..5132770fa5a 100644 --- a/packages/orchestration/test/facade.test.ts +++ b/packages/orchestration/test/facade.test.ts @@ -76,7 +76,7 @@ test.serial('chain info', async t => { return orc.getChain('mock'); }); - const result = (await handle()) as Chain; + const result = (await vt.when(handle())) as Chain; t.deepEqual(await vt.when(result.getChainInfo()), mockChainInfo); }); @@ -122,7 +122,7 @@ test.serial('faulty chain info', async t => { return account; }); - await t.throwsAsync(handle(), { + await t.throwsAsync(vt.when(handle()), { message: 'chain info lacks staking denom', }); }); diff --git a/packages/orchestration/test/types.test-d.ts b/packages/orchestration/test/types.test-d.ts index 323b382cadf..4c55ba315e6 100644 --- a/packages/orchestration/test/types.test-d.ts +++ b/packages/orchestration/test/types.test-d.ts @@ -5,19 +5,24 @@ import { expectNotType, expectType } from 'tsd'; import { typedJson } from '@agoric/cosmic-proto'; import type { MsgDelegateResponse } from '@agoric/cosmic-proto/cosmos/staking/v1beta1/tx.js'; import type { QueryAllBalancesResponse } from '@agoric/cosmic-proto/cosmos/bank/v1beta1/query.js'; -import type { Vow } from '@agoric/vow'; +import type { Vow, VowTools } from '@agoric/vow'; import type { GuestAsyncFunc, HostInterface, HostOf } from '@agoric/async-flow'; import type { ChainAddress, CosmosValidatorAddress, StakingAccountActions, OrchestrationAccount, + Orchestrator, } from '../src/types.js'; import type { LocalOrchestrationAccountKit } from '../src/exos/local-orchestration-account.js'; import { prepareCosmosOrchestrationAccount } from '../src/exos/cosmos-orchestration-account.js'; +import type { OrchestrationFacade } from '../src/facade.js'; +import type { ResolvedContinuingOfferResult } from '../src/utils/zoe-tools.js'; const anyVal = null as any; +const vt: VowTools = null as any; + const validatorAddr = { chainId: 'agoric3', value: 'agoric1valoperhello', @@ -91,7 +96,7 @@ expectNotType(chainAddr); expectNotType<() => Promise>(vowFn); } -// PromiseToVow with TransferSteps +// HostOf with TransferSteps { type TransferStepsVow = HostOf['transferSteps']>; @@ -122,3 +127,31 @@ expectNotType(chainAddr); bizz: () => Record; }>(vowObject); } + +{ + // orchestrate() + + const facade: OrchestrationFacade = null as any; + const echo = (orc: Orchestrator, ctx: undefined, num: T) => + num; + // @ts-expect-error requires an async function + facade.orchestrate('name', undefined, echo); + + const slowEcho = ( + orc: Orchestrator, + ctx: undefined, + num: T, + ) => Promise.resolve(num); + { + const h = facade.orchestrate('name', undefined, slowEcho); + expectType>(h(42)); + expectType>(h(42)); + } + + const makeOfferResult = () => + Promise.resolve({} as ResolvedContinuingOfferResult); + { + const h = facade.orchestrate('name', undefined, makeOfferResult); + expectType>(h()); + } +} diff --git a/packages/solo/package.json b/packages/solo/package.json index 70aa147efed..2bbb646e661 100644 --- a/packages/solo/package.json +++ b/packages/solo/package.json @@ -78,6 +78,6 @@ "workerThreads": false }, "typeCoverage": { - "atLeast": 73.21 + "atLeast": 73.36 } } diff --git a/packages/time/package.json b/packages/time/package.json index ed5962f41fb..12501d1b485 100644 --- a/packages/time/package.json +++ b/packages/time/package.json @@ -58,6 +58,6 @@ "access": "public" }, "typeCoverage": { - "atLeast": 88.77 + "atLeast": 88.72 } } diff --git a/packages/vow/package.json b/packages/vow/package.json index cf76ec21198..b17ecbb86c6 100755 --- a/packages/vow/package.json +++ b/packages/vow/package.json @@ -54,6 +54,6 @@ "access": "public" }, "typeCoverage": { - "atLeast": 90.06 + "atLeast": 89.93 } } diff --git a/packages/zoe/package.json b/packages/zoe/package.json index 834acd44146..ea3d903a602 100644 --- a/packages/zoe/package.json +++ b/packages/zoe/package.json @@ -141,6 +141,6 @@ "access": "public" }, "typeCoverage": { - "atLeast": 85 + "atLeast": 84.99 } }