From 884a94bc50e70332feab5e5b1dad11ce783bd7e9 Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Wed, 10 Jul 2024 14:26:15 -0700 Subject: [PATCH 01/20] chore(types): update coverage measurement --- packages/async-flow/package.json | 2 +- packages/orchestration/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/async-flow/package.json b/packages/async-flow/package.json index abb441eb65b..26b3296e454 100644 --- a/packages/async-flow/package.json +++ b/packages/async-flow/package.json @@ -60,6 +60,6 @@ "workerThreads": false }, "typeCoverage": { - "atLeast": 77.83 + "atLeast": 76.14 } } diff --git a/packages/orchestration/package.json b/packages/orchestration/package.json index 46239068694..0dafc15bdf1 100644 --- a/packages/orchestration/package.json +++ b/packages/orchestration/package.json @@ -89,6 +89,6 @@ "access": "public" }, "typeCoverage": { - "atLeast": 97.1 + "atLeast": 97.89 } } From 94dfab1cfd960aada452753b1482ed797bc2da13 Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Wed, 10 Jul 2024 12:42:28 -0700 Subject: [PATCH 02/20] feat: error on membrane promise --- packages/async-flow/src/replay-membrane.js | 72 ++-------------------- packages/async-flow/test/bad-host.test.js | 4 +- 2 files changed, 8 insertions(+), 68 deletions(-) diff --git a/packages/async-flow/src/replay-membrane.js b/packages/async-flow/src/replay-membrane.js index 44ed86848e7..16e19f6a749 100644 --- a/packages/async-flow/src/replay-membrane.js +++ b/packages/async-flow/src/replay-membrane.js @@ -1,21 +1,13 @@ /* eslint-disable no-use-before-define */ +import { isVow } from '@agoric/vow/src/vow-utils.js'; +import { heapVowE } from '@agoric/vow/vat.js'; +import { throwLabeled } from '@endo/common/throw-labeled.js'; import { Fail, X, b, makeError, q } from '@endo/errors'; import { E } from '@endo/eventual-send'; import { getMethodNames } from '@endo/eventual-send/utils.js'; -import { throwLabeled } from '@endo/common/throw-labeled.js'; -import { objectMap } from '@endo/common/object-map.js'; -import { - Far, - Remotable, - getInterfaceOf, - getTag, - makeTagged, - passStyleOf, -} from '@endo/pass-style'; -import { heapVowE } from '@agoric/vow/vat.js'; -import { isVow } from '@agoric/vow/src/vow-utils.js'; -import { makeEquate } from './equate.js'; +import { Far, Remotable, getInterfaceOf } from '@endo/pass-style'; import { makeConvertKit } from './convert.js'; +import { makeEquate } from './equate.js'; /** * @import {PromiseKit} from '@endo/promise-kit' @@ -43,7 +35,7 @@ export const makeReplayMembrane = ({ watchWake, panic, }) => { - const { when, watch, makeVowKit } = vowTools; + const { when, makeVowKit } = vowTools; const equate = makeEquate(bijection); @@ -137,63 +129,12 @@ export const makeReplayMembrane = ({ // ///////////// Guest to Host or consume log //////////////////////////////// - /** - * The host is not supposed to expose host-side promises to the membrane, - * since they cannot be stored durably or survive upgrade. We cannot just - * automatically wrap any such host promises with host vows, because that - * would mask upgrade hazards if an upgrade happens before the vow settles. - * However, during the transition, the current host APIs called by - * orchestration still return many promises. We want to generate diagnostics - * when we encounter them, but for now, automatically convert them to - * host vow anyway, just so integration testing can proceed to reveal - * additional problems beyond these. - * - * @param {Passable} h - */ - const tolerateHostPromiseToVow = h => { - const passStyle = passStyleOf(h); - switch (passStyle) { - case 'promise': { - const e = Error('where warning happened'); - console.log('Warning for now: vow expected, not promise', h, e); - // TODO remove this stopgap. Here for now because host-side - // promises are everywhere! - // Note: A good place to set a breakpoint, or to uncomment the - // `debugger;` line, to work around bundling. - // debugger; - return watch(h); - } - case 'copyRecord': { - const o = /** @type {object} */ (h); - return objectMap(o, tolerateHostPromiseToVow); - } - case 'copyArray': { - const a = /** @type {Array} */ (h); - return harden(a.map(tolerateHostPromiseToVow)); - } - case 'tagged': { - const t = /** @type {CopyTagged} */ (h); - if (isVow(t)) { - return h; - } - return makeTagged(getTag(t), tolerateHostPromiseToVow(t.payload)); - } - default: { - return h; - } - } - }; - const performCall = (hostTarget, optVerb, hostArgs, callIndex) => { let hostResult; try { hostResult = optVerb ? hostTarget[optVerb](...hostArgs) : hostTarget(...hostArgs); - // This is a temporary kludge anyway. But note that it only - // catches the case where the promise is at the top of hostResult. - harden(hostResult); - hostResult = tolerateHostPromiseToVow(hostResult); // Try converting here just to route the error correctly hostToGuest(hostResult, `converting ${optVerb || 'host'} result`); } catch (hostProblem) { @@ -575,7 +516,6 @@ export const makeReplayMembrane = ({ * @returns {Promise} */ const makeGuestForHostVow = (hVow, promiseKey = undefined) => { - hVow = tolerateHostPromiseToVow(hVow); isVow(hVow) || Fail`vow expected ${hVow}`; const { promise, resolve, reject } = makeGuestPromiseKit(); promiseKey ??= promise; diff --git a/packages/async-flow/test/bad-host.test.js b/packages/async-flow/test/bad-host.test.js index d9e80944afd..a7d3bb8893c 100644 --- a/packages/async-flow/test/bad-host.test.js +++ b/packages/async-flow/test/bad-host.test.js @@ -141,7 +141,7 @@ const testBadHostReplay1 = async (t, zone) => { }, { message: - 'Remotables must be explicitly declared: "[Function nonPassableFunc]"', + 'converting badMethod result: Remotables must be explicitly declared: "[Function nonPassableFunc]"', }, ); t.log(' badHost replay1 guest error caused by host error', gErr); @@ -177,7 +177,7 @@ const testBadHostReplay1 = async (t, zone) => { 'doThrow', 2, Error( - 'Remotables must be explicitly declared: "[Function nonPassableFunc]"', + 'converting badMethod result: Remotables must be explicitly declared: "[Function nonPassableFunc]"', ), ], ['checkCall', badHost, 'badMethod', [], 4], From 600598473736848279707a1d438f1e4b81833fce Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Wed, 10 Jul 2024 13:05:10 -0700 Subject: [PATCH 03/20] test(types): retriable --- packages/vow/package.json | 3 ++- packages/vow/src/tools.js | 22 ++-------------------- packages/vow/test/types.test-d.ts | 18 ++++++++++++++++++ 3 files changed, 22 insertions(+), 21 deletions(-) create mode 100644 packages/vow/test/types.test-d.ts diff --git a/packages/vow/package.json b/packages/vow/package.json index fb69de3aa1d..e304c696c14 100755 --- a/packages/vow/package.json +++ b/packages/vow/package.json @@ -33,7 +33,8 @@ "@agoric/internal": "^0.3.2", "@endo/far": "^1.1.2", "@endo/init": "^1.1.2", - "ava": "^5.3.0" + "ava": "^5.3.0", + "tsd": "^0.31.1" }, "ava": { "require": [ diff --git a/packages/vow/src/tools.js b/packages/vow/src/tools.js index 26e8dca10e7..ec224cbe078 100644 --- a/packages/vow/src/tools.js +++ b/packages/vow/src/tools.js @@ -10,22 +10,6 @@ import { makeWhen } from './when.js'; * @import {IsRetryableReason, AsPromiseFunction, EVow, Vow, ERef} from './types.js'; */ -// TODO find a good home, DRY with orchestration package. -/** - * Converts a function type that returns a Promise to a function type that - * returns a Vow. If the input is not a function returning a Promise, it - * preserves the original type. - * - * @template T - The type to transform - * @typedef {T extends ( - * ...args: infer Args - * ) => Promise - * ? (...args: Args) => Vow - * : T extends (...args: infer Args) => infer R - * ? (...args: Args) => R - * : T} PromiseToVow - */ - /** * NB: Not to be used in a Vat. It doesn't know what an upgrade is. For that you * need `prepareVowTools` from `vat.js`. @@ -54,13 +38,11 @@ export const prepareVowTools = (zone, powers = {}) => { * Create a function that retries the given function if the underlying * functions rejects due to upgrade disconnection. * - * The internal functions - * - * @template {(...args: any[]) => any} F + * @template {(...args: any[]) => Promise} F * @param {Zone} fnZone - the zone for the named function * @param {string} name * @param {F} fn - * @returns {PromiseToVow} + * @returns {F extends (...args: infer Args) => Promise ? (...args: Args) => Vow : never} */ const retriable = (fnZone, name, fn) => diff --git a/packages/vow/test/types.test-d.ts b/packages/vow/test/types.test-d.ts new file mode 100644 index 00000000000..8a859348caf --- /dev/null +++ b/packages/vow/test/types.test-d.ts @@ -0,0 +1,18 @@ +import { expectType } from 'tsd'; +import type { Zone } from '@agoric/base-zone'; +import type { Vow } from '../src/types.js'; +import type { VowTools } from '../src/tools.js'; + +const vt: VowTools = null as any; + +const zone: Zone = null as any; + +// @ts-expect-error function param must return promise +vt.retriable(zone, 'foo', () => null); +vt.retriable(zone, 'foo', () => Promise.resolve(null)); + +expectType<(p1: number, p2: string) => Vow<{ someValue: 'bar' }>>( + vt.retriable(zone, 'foo', (p1: number, p2: string) => + Promise.resolve({ someValue: 'bar' } as const), + ), +); From d91d11d3e8ff68407af0ff4552e9051341418611 Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Wed, 10 Jul 2024 13:23:10 -0700 Subject: [PATCH 04/20] refactor: types to .d.ts --- packages/async-flow/src/types.d.ts | 157 +++++++++++++++++++++++ packages/async-flow/src/types.js | 197 +---------------------------- 2 files changed, 158 insertions(+), 196 deletions(-) create mode 100644 packages/async-flow/src/types.d.ts diff --git a/packages/async-flow/src/types.d.ts b/packages/async-flow/src/types.d.ts new file mode 100644 index 00000000000..efd7d9bd6d4 --- /dev/null +++ b/packages/async-flow/src/types.d.ts @@ -0,0 +1,157 @@ +import type { Passable } from '@endo/pass-style'; +import type { Vow, VowTools } from '@agoric/vow'; +import type { LogStore } from './log-store.js'; +import type { Bijection } from './bijection.js'; +import type { EndowmentTools } from './endowments.js'; + +export type FlowState = + | 'Running' + | 'Sleeping' + | 'Replaying' + | 'Failed' + | 'Done'; +/** + * `T` defaults to `any`, not `Passable`, because unwrapped guests include + * non-passables, like unwrapped functions and unwrapped state records. + * (Unwrapped functions could be made into Remotables, + * but since they still could not be made durable, in this context + * it'd be pointless.) + */ +export type Guest = T; +export type Host = T; +/** + * A HostVow must be durably storable. It corresponds to an + * ephemeral guest promise. + */ +export type HostVow = Host>; +export type GuestAsyncFunc = ( + ...activationArgs: Guest[] +) => Guest>; +export type HostAsyncFuncWrapper = (...activationArgs: Host[]) => HostVow; +export type PreparationOptions = { + vowTools?: VowTools; + makeLogStore?: (() => LogStore) | undefined; + makeBijection?: (() => Bijection) | undefined; + endowmentTools?: EndowmentTools; +}; +export type OutcomeKind = 'return' | 'throw'; +export type Outcome = + | { + kind: 'return'; + result: any; + } + | { + kind: 'throw'; + problem: any; + }; +export type Ephemera = { + for: (self: S) => V; + resetFor: (self: S) => void; +}; + +/** + * This is the type alias for the membrane log entries we currently implement. + * + * @see {FutureLogEntry} below for the full membrane log entry, which we do not + * yet support. + */ +export type LogEntry = + | [ + // ///////////////// From Host to Guest ///////////////////////// + op: 'doFulfill', + vow: HostVow, + fulfillment: Host, + ] + | [op: 'doReject', vow: HostVow, reason: Host] + | [op: 'doReturn', callIndex: number, result: Host] + | [op: 'doThrow', callIndex: number, problem: Host] + | [ + // ///////////////////// From Guest to Host ///////////////////////// + op: 'checkCall', + target: Host, + optVerb: PropertyKey | undefined, + args: Host[], + callIndex: number, + ] + | [ + op: 'checkSendOnly', + target: Host, + optVerb: PropertyKey | undefined, + args: Host[], + callIndex: number, + ] + | [ + op: 'checkSend', + target: Host, + optVerb: PropertyKey | undefined, + args: Host[], + callIndex: number, + ]; + +/** + * This would be the type alias for the full membrane log, if we supported: + * - the guest sending guest-promises and guest-remotables to the host + * - the guest using `E` to eventual-send to guest wrappers of the host + * vows and remotables. + */ +export type FutureLogEntry = + | [ + // ///////////////// From Host to Guest /////////////////////// + op: 'doFulfill', + vow: HostVow, + fulfillment: Host, + ] + | [op: 'doReject', vow: HostVow, reason: Host] + | [ + op: 'doCall', + target: Host, + optVerb: PropertyKey | undefined, + args: Host[], + callIndex: number, + ] + | [ + op: 'doSendOnly', + target: Host, + optVerb: PropertyKey | undefined, + args: Host[], + callIndex: number, + ] + | [ + op: 'doSend', + target: Host, + optVerb: PropertyKey | undefined, + args: Host[], + callIndex: number, + ] + | [op: 'doReturn', callIndex: number, result: Host] + | [op: 'doThrow', callIndex: number, problem: Host] + | [ + // ///////////////////// From Guest to Host ///////////////////////// + op: 'checkFulfill', + vow: HostVow, + fulfillment: Host, + ] + | [op: 'checkReject', vow: HostVow, reason: Host] + | [ + op: 'checkCall', + target: Host, + optVerb: PropertyKey | undefined, + args: Host[], + callIndex: number, + ] + | [ + op: 'checkSendOnly', + target: Host, + optVerb: PropertyKey | undefined, + args: Host[], + callIndex: number, + ] + | [ + op: 'checkSend', + target: Host, + optVerb: PropertyKey | undefined, + args: Host[], + callIndex: number, + ] + | [op: 'checkReturn', callIndex: number, result: Host] + | [op: 'checkThrow', callIndex: number, problem: Host]; diff --git a/packages/async-flow/src/types.js b/packages/async-flow/src/types.js index 8ecaeece849..dc3dd54bc84 100644 --- a/packages/async-flow/src/types.js +++ b/packages/async-flow/src/types.js @@ -1,196 +1 @@ -// Ensure this is a module. -export {}; - -/** - * @import {Passable} from '@endo/pass-style' - * @import {Vow, VowTools} from '@agoric/vow' - * @import {LogStore} from './log-store.js' - * @import {Bijection} from './bijection.js' - * @import {EndowmentTools} from './endowments.js' - */ - -/** - * @typedef {'Running' | - * 'Sleeping' | - * 'Replaying' | - * 'Failed' | - * 'Done' - * } FlowState - */ - -/** - * `T` defaults to `any`, not `Passable`, because unwrapped guests include - * non-passables, like unwrapped functions and unwrapped state records. - * (Unwrapped functions could be made into Remotables, - * but since they still could not be made durable, in this context - * it'd be pointless.) - * - * @template {any} [T=any] - * @typedef {T} Guest - */ - -/** - * @template {Passable} [T=Passable] - * @typedef {T} Host - */ - -/** - * A HostVow must be durably storable. It corresponds to an - * ephemeral guest promise. - * - * @template {Passable} [T=Passable] - * @typedef {Host>} HostVow - */ - -/** - * @typedef {(...activationArgs: Guest[]) => Guest} GuestAsyncFunc - */ - -/** - * @typedef {(...activationArgs: Host[]) => HostVow} HostAsyncFuncWrapper - */ - -/** - * @typedef {object} PreparationOptions - * @property {VowTools} [vowTools] - * @property {() => LogStore} [makeLogStore] - * @property {() => Bijection} [makeBijection] - * @property {EndowmentTools} [endowmentTools] - */ - -/** - * @typedef {'return'|'throw'} OutcomeKind - */ - -/** - * @typedef {{kind: 'return', result: any} - * | {kind: 'throw', problem: any} - * } Outcome - */ - -/** - * @template {WeakKey} [S=WeakKey] - * @template {any} [V=any] - * @typedef {object} Ephemera - * @property {(self: S) => V} for - * @property {(self: S) => void} resetFor - */ - -/** - * This is the typedef for the membrane log entries we currently implement. - * See comment below for the commented-out typedef for the full - * membrane log entry, which we do not yet support. - * - * @typedef {[ // ///////////////// From Host to Guest ///////////////////////// - * op: 'doFulfill', - * vow: HostVow, - * fulfillment: Host, - * ] | [ - * op: 'doReject', - * vow: HostVow, - * reason: Host, - * ] | [ - * op: 'doReturn', - * callIndex: number, - * result: Host, - * ] | [ - * op: 'doThrow', - * callIndex: number, - * problem: Host, - * ] | [ // ///////////////////// From Guest to Host ///////////////////////// - * op: 'checkCall', - * target: Host, - * optVerb: PropertyKey|undefined, - * args: Host[], - * callIndex: number - * ] | [ - * op: 'checkSendOnly', - * target: Host, - * optVerb: PropertyKey|undefined, - * args: Host[], - * callIndex: number - * ] | [ - * op: 'checkSend', - * target: Host, - * optVerb: PropertyKey|undefined, - * args: Host[], - * callIndex: number - * ]} LogEntry - */ - -/** - * This would be the typedef for the full membrane log, if we supported - * - the guest sending guest-promises and guest-remotables to the host - * - the guest using `E` to eventual-send to guest wrappers of host - * vows and remotables. - * - * at-typedef {[ // ///////////////// From Host to Guest /////////////////////// - * op: 'doFulfill', - * vow: HostVow, - * fulfillment: Host, - * ] | [ - * op: 'doReject', - * vow: HostVow, - * reason: Host, - * ] | [ - * op: 'doCall', - * target: Host, - * optVerb: PropertyKey|undefined, - * args: Host[], - * callIndex: number - * ] | [ - * op: 'doSendOnly', - * target: Host, - * optVerb: PropertyKey|undefined, - * args: Host[], - * callIndex: number - * ] | [ - * op: 'doSend', - * target: Host, - * optVerb: PropertyKey|undefined, - * args: Host[], - * callIndex: number - * ] | [ - * op: 'doReturn', - * callIndex: number, - * result: Host, - * ] | [ - * op: 'doThrow', - * callIndex: number, - * problem: Host, - * ] | [ // ///////////////////// From Guest to Host ///////////////////////// - * op: 'checkFulfill', - * vow: HostVow, - * fulfillment: Host, - * ] | [ - * op: 'checkReject', - * vow: HostVow, - * reason: Host, - * ] | [ - * op: 'checkCall', - * target: Host, - * optVerb: PropertyKey|undefined, - * args: Host[], - * callIndex: number - * ] | [ - * op: 'checkSendOnly', - * target: Host, - * optVerb: PropertyKey|undefined, - * args: Host[], - * callIndex: number - * ] | [ - * op: 'checkSend', - * target: Host, - * optVerb: PropertyKey|undefined, - * args: Host[], - * callIndex: number - * ] | [ - * op: 'checkReturn', - * callIndex: number, - * result: Host, - * ] | [ - * op: 'checkThrow', - * callIndex: number, - * problem: Host, - * ]} LogEntry - */ +export const empty = 'Empty JS file to correspond with its .d.ts twin'; From 35380af4bbda51be2a9cd047f9c4992791090e94 Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Wed, 10 Jul 2024 13:54:36 -0700 Subject: [PATCH 05/20] feat(types): HostOf, GuestOf --- packages/async-flow/index.js | 1 + packages/async-flow/package.json | 5 ++-- packages/async-flow/src/types.d.ts | 34 ++++++++++++++++++++++++ packages/async-flow/test/types.test-d.ts | 22 +++++++++++++++ packages/async-flow/tsconfig.json | 2 ++ yarn.lock | 29 ++++++++++++++------ 6 files changed, 83 insertions(+), 10 deletions(-) create mode 100644 packages/async-flow/test/types.test-d.ts diff --git a/packages/async-flow/index.js b/packages/async-flow/index.js index a27161c5a8c..7c6e499a9c0 100644 --- a/packages/async-flow/index.js +++ b/packages/async-flow/index.js @@ -1,2 +1,3 @@ export * from './src/async-flow.js'; +export * from './src/types.js'; export { makeStateRecord } from './src/endowments.js'; diff --git a/packages/async-flow/package.json b/packages/async-flow/package.json index 26b3296e454..520f2490d52 100644 --- a/packages/async-flow/package.json +++ b/packages/async-flow/package.json @@ -28,11 +28,11 @@ "@agoric/internal": "^0.3.2", "@agoric/store": "^0.9.2", "@agoric/vow": "^0.1.0", - "@endo/pass-style": "^1.4.0", "@endo/common": "^1.2.2", "@endo/errors": "^1.2.2", "@endo/eventual-send": "^1.2.2", "@endo/marshal": "^1.5.0", + "@endo/pass-style": "^1.4.0", "@endo/patterns": "^1.4.0", "@endo/promise-kit": "^1.1.2" }, @@ -41,7 +41,8 @@ "@agoric/zone": "^0.2.2", "@endo/env-options": "^1.1.4", "@endo/ses-ava": "^1.2.2", - "ava": "^5.3.0" + "ava": "^5.3.0", + "tsd": "^0.31.1" }, "publishConfig": { "access": "public" diff --git a/packages/async-flow/src/types.d.ts b/packages/async-flow/src/types.d.ts index efd7d9bd6d4..95b2b6f6a6e 100644 --- a/packages/async-flow/src/types.d.ts +++ b/packages/async-flow/src/types.d.ts @@ -10,6 +10,7 @@ export type FlowState = | 'Replaying' | 'Failed' | 'Done'; + /** * `T` defaults to `any`, not `Passable`, because unwrapped guests include * non-passables, like unwrapped functions and unwrapped state records. @@ -19,15 +20,46 @@ export type FlowState = */ export type Guest = T; export type Host = T; + /** * A HostVow must be durably storable. It corresponds to an * ephemeral guest promise. */ export type HostVow = Host>; + export type GuestAsyncFunc = ( ...activationArgs: Guest[] ) => Guest>; + export type HostAsyncFuncWrapper = (...activationArgs: Host[]) => HostVow; + +/** + * The function from the host as it will be available in the guest. + * + * Specifically, Vow return values are converted to Promises. + */ +export type GuestOf = F extends ( + ...args: infer A +) => Vow + ? (...args: A) => Promise + : F; + +/** + * Convert an entire Guest interface into what the host will implement. + */ +type HostInterface = { + [K in keyof T]: HostOf; +}; + +/** + * The function the host must provide to match an interface the guest expects. + * + * Specifically, Promise return values are converted to Vows. + */ +export type HostOf = F extends (...args: infer A) => Promise + ? (...args: A) => Vow> + : F; + export type PreparationOptions = { vowTools?: VowTools; makeLogStore?: (() => LogStore) | undefined; @@ -35,6 +67,7 @@ export type PreparationOptions = { endowmentTools?: EndowmentTools; }; export type OutcomeKind = 'return' | 'throw'; + export type Outcome = | { kind: 'return'; @@ -44,6 +77,7 @@ export type Outcome = kind: 'throw'; problem: any; }; + export type Ephemera = { for: (self: S) => V; resetFor: (self: S) => void; diff --git a/packages/async-flow/test/types.test-d.ts b/packages/async-flow/test/types.test-d.ts new file mode 100644 index 00000000000..9c393769464 --- /dev/null +++ b/packages/async-flow/test/types.test-d.ts @@ -0,0 +1,22 @@ +import { expectType } from 'tsd'; +import type { Zone } from '@agoric/base-zone'; +import type { Vow, VowTools } from '@agoric/vow'; +import type { HostOf, GuestOf } from '../src/types.js'; + +const vt: VowTools = null as any; + +const sumVow = (a: number, b: number) => vt.asVow(() => a + b); + +const sumPromise = (a: number, b: number) => Promise.resolve(a + b); + +expectType<(p1: number, p2: number) => Promise>( + null as unknown as GuestOf, +); + +expectType<(p1: number, p2: number) => Vow>( + null as unknown as HostOf, +); +expectType<(p1: number, p2: number) => Vow>( + // @ts-expect-error incompatible return type + null as unknown as HostOf, +); diff --git a/packages/async-flow/tsconfig.json b/packages/async-flow/tsconfig.json index 028175d9012..80197fab9ef 100644 --- a/packages/async-flow/tsconfig.json +++ b/packages/async-flow/tsconfig.json @@ -9,5 +9,7 @@ "scripts", "src/**/*.js", "test/**/*.js", + "src/**/*.ts", + "test/**/*.ts", ], } diff --git a/yarn.lock b/yarn.lock index 5b6cb3a4b97..8762b87847b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3723,13 +3723,18 @@ resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-7.15.0.tgz#fb894373a6e3882cbb37671ffddce44f934f62fc" integrity sha512-aV1+B1+ySXbQH0pLK0rx66I3IkiZNidYobyfn0WFsdGhSXw+P3YOqeTq5GED458SfB24tg+ux3S+9g118hjlTw== +"@typescript-eslint/types@7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-7.16.0.tgz#60a19d7e7a6b1caa2c06fac860829d162a036ed2" + integrity sha512-fecuH15Y+TzlUutvUl9Cc2XJxqdLr7+93SQIbcZfd4XRGGKoxyljK27b+kxKamjRkU7FYC6RrbSCg0ALcZn/xw== + "@typescript-eslint/typescript-estree@7.15.0", "@typescript-eslint/typescript-estree@^7.15.0": - version "7.15.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-7.15.0.tgz#e323bfa3966e1485b638ce751f219fc1f31eba37" - integrity sha512-gjyB/rHAopL/XxfmYThQbXbzRMGhZzGw6KpcMbfe8Q3nNQKStpxnUKeXb0KiN/fFDR42Z43szs6rY7eHk0zdGQ== + version "7.16.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-7.16.0.tgz#98ac779d526fab2a781e5619c9250f3e33867c09" + integrity sha512-a5NTvk51ZndFuOLCh5OaJBELYc2O3Zqxfl3Js78VFE1zE46J2AaVuW+rEbVkQznjkmlzWsUI15BG5tQMixzZLw== dependencies: - "@typescript-eslint/types" "7.15.0" - "@typescript-eslint/visitor-keys" "7.15.0" + "@typescript-eslint/types" "7.16.0" + "@typescript-eslint/visitor-keys" "7.16.0" debug "^4.3.4" globby "^11.1.0" is-glob "^4.0.3" @@ -3755,6 +3760,14 @@ "@typescript-eslint/types" "7.15.0" eslint-visitor-keys "^3.4.3" +"@typescript-eslint/visitor-keys@7.16.0": + version "7.16.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-7.16.0.tgz#a1d99fa7a3787962d6e0efd436575ef840e23b06" + integrity sha512-rMo01uPy9C7XxG7AFsxa8zLnWXTF8N3PYclekWSrurvhwiw1eW88mrKiAYe6s53AUY57nTRz8dJsuuXdkAhzCg== + dependencies: + "@typescript-eslint/types" "7.16.0" + eslint-visitor-keys "^3.4.3" + "@ungap/structured-clone@^1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" @@ -9986,9 +9999,9 @@ proto-list@~1.2.1: integrity sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk= protobufjs@^6.8.8, protobufjs@^7.2.4, protobufjs@^7.2.6: - version "7.3.0" - resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.3.0.tgz#a32ec0422c039798c41a0700306a6e305b9cb32c" - integrity sha512-YWD03n3shzV9ImZRX3ccbjqLxj7NokGN0V/ESiBV5xWqrommYHYiihuIyavq03pWSGqlyvYUFmfoMKd+1rPA/g== + version "7.3.2" + resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.3.2.tgz#60f3b7624968868f6f739430cfbc8c9370e26df4" + integrity sha512-RXyHaACeqXeqAKGLDl68rQKbmObRsTIn4TYVUUug1KfS47YWCo5MacGITEryugIgZqORCvJWEk4l449POg5Txg== dependencies: "@protobufjs/aspromise" "^1.1.2" "@protobufjs/base64" "^1.1.2" From da4380db835f83ec18674d908e331abb5b1d85f1 Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Wed, 10 Jul 2024 14:25:07 -0700 Subject: [PATCH 06/20] chore: update type-coverage --- packages/async-flow/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/async-flow/package.json b/packages/async-flow/package.json index 520f2490d52..3a6903cad39 100644 --- a/packages/async-flow/package.json +++ b/packages/async-flow/package.json @@ -61,6 +61,6 @@ "workerThreads": false }, "typeCoverage": { - "atLeast": 76.14 + "atLeast": 77.32 } } From 541900fb6d30c2a2ad98e75055a4042dcc4d9d02 Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Wed, 10 Jul 2024 13:54:56 -0700 Subject: [PATCH 07/20] refactor(types): use HostOf --- .../src/exos/local-chain-facade.js | 4 +-- .../src/exos/local-orchestration-account.js | 13 +++---- .../orchestration/src/exos/orchestrator.js | 8 ++--- .../src/exos/remote-chain-facade.js | 36 +++++++++---------- 4 files changed, 29 insertions(+), 32 deletions(-) diff --git a/packages/orchestration/src/exos/local-chain-facade.js b/packages/orchestration/src/exos/local-chain-facade.js index 357ebcb3120..79902b9594a 100644 --- a/packages/orchestration/src/exos/local-chain-facade.js +++ b/packages/orchestration/src/exos/local-chain-facade.js @@ -14,7 +14,7 @@ import { ChainFacadeI } from '../typeGuards.js'; * @import {Vow, VowTools} from '@agoric/vow'; * @import {CosmosInterchainService} from './cosmos-interchain-service.js'; * @import {MakeLocalOrchestrationAccountKit} from './local-orchestration-account.js'; - * @import {ChainAddress, ChainInfo, CosmosChainInfo, IBCConnectionInfo, OrchestrationAccount, PromiseToVow} from '../types.js'; + * @import {ChainAddress, ChainInfo, CosmosChainInfo, IBCConnectionInfo, OrchestrationAccount} from '../types.js'; */ /** @@ -72,7 +72,7 @@ const prepareLocalChainFacadeKit = ( // FIXME parameterize on the remoteChainInfo to make() // That used to work but got lost in the migration to Exo - /** @returns {Vow>>} */ + /** @returns {Vow>} */ makeAccount() { const lcaP = E(localchain).makeAccount(); // TODO #9449 fix types diff --git a/packages/orchestration/src/exos/local-orchestration-account.js b/packages/orchestration/src/exos/local-orchestration-account.js index 55deed6e58b..bdc7ad63404 100644 --- a/packages/orchestration/src/exos/local-orchestration-account.js +++ b/packages/orchestration/src/exos/local-orchestration-account.js @@ -19,8 +19,9 @@ import { orchestrationAccountMethods } from '../utils/orchestrationAccount.js'; import { makeTimestampHelper } from '../utils/time.js'; /** + * @import {HostOf} from '@agoric/async-flow'; * @import {LocalChainAccount} from '@agoric/vats/src/localchain.js'; - * @import {AmountArg, ChainAddress, DenomAmount, IBCMsgTransferOptions, OrchestrationAccount, ChainInfo, IBCConnectionInfo, PromiseToVow} from '@agoric/orchestration'; + * @import {AmountArg, ChainAddress, DenomAmount, IBCMsgTransferOptions, OrchestrationAccount, ChainInfo, IBCConnectionInfo} from '@agoric/orchestration'; * @import {RecorderKit, MakeRecorderKit} from '@agoric/zoe/src/contractSupport/recorder.js'. * @import {Zone} from '@agoric/zone'; * @import {Remote} from '@agoric/internal'; @@ -306,7 +307,7 @@ export const prepareLocalOrchestrationAccountKit = ( /** * TODO: balance lookups for non-vbank assets * - * @type {PromiseToVow['getBalance']>} + * @type {HostOf['getBalance']>} */ getBalance(denomArg) { // FIXME look up real values @@ -393,18 +394,18 @@ export const prepareLocalOrchestrationAccountKit = ( * updater will get a special notification that the account is being * transferred. */ - /** @type {PromiseToVow} */ + /** @type {HostOf} */ deposit(payment) { return watch( E(this.state.account).deposit(payment), this.facets.returnVoidWatcher, ); }, - /** @type {PromiseToVow} */ + /** @type {HostOf} */ withdraw(amount) { return watch(E(this.state.account).withdraw(amount)); }, - /** @type {PromiseToVow} */ + /** @type {HostOf} */ executeTx(messages) { return watch(E(this.state.account).executeTx(messages)); }, @@ -456,7 +457,7 @@ export const prepareLocalOrchestrationAccountKit = ( return watch(transferV, this.facets.returnVoidWatcher); }); }, - /** @type {PromiseToVow['transferSteps']>} */ + /** @type {HostOf['transferSteps']>} */ transferSteps(amount, msg) { return asVow(() => { console.log('transferSteps got', amount, msg); diff --git a/packages/orchestration/src/exos/orchestrator.js b/packages/orchestration/src/exos/orchestrator.js index 3fc2b80134f..2b4a69c2ed3 100644 --- a/packages/orchestration/src/exos/orchestrator.js +++ b/packages/orchestration/src/exos/orchestrator.js @@ -15,7 +15,7 @@ import { /** * @import {Zone} from '@agoric/base-zone'; * @import {ChainHub} from './chain-hub.js'; - * @import {AsyncFlowTools} from '@agoric/async-flow'; + * @import {AsyncFlowTools, HostOf} from '@agoric/async-flow'; * @import {Vow, VowTools} from '@agoric/vow'; * @import {TimerService} from '@agoric/time'; * @import {LocalChain} from '@agoric/vats/src/localchain.js'; @@ -26,7 +26,7 @@ import { * @import {MakeLocalOrchestrationAccountKit} from './local-orchestration-account.js'; * @import {MakeLocalChainFacade} from './local-chain-facade.js'; * @import {MakeRemoteChainFacade} from './remote-chain-facade.js'; - * @import {Chain, ChainInfo, CosmosChainInfo, IBCConnectionInfo, OrchestrationAccount, Orchestrator, PromiseToVow} from '../types.js'; + * @import {Chain, ChainInfo, IBCConnectionInfo, Orchestrator} from '../types.js'; */ const { Fail } = assert; @@ -113,7 +113,7 @@ export const prepareOrchestratorKit = ( }, }, orchestrator: { - /** @type {PromiseToVow} */ + /** @type {HostOf} */ getChain(name) { if (name === 'agoric') { // TODO #9449 fix types @@ -123,8 +123,6 @@ export const prepareOrchestratorKit = ( this.facets.makeLocalChainFacadeWatcher, ); } - // TODO #9449 fix types - // @ts-expect-error Type 'Vow' is not assignable to type 'Vow>'. return watch( chainHub.getChainsAndConnection('agoric', name), this.facets.makeRemoteChainFacadeWatcher, diff --git a/packages/orchestration/src/exos/remote-chain-facade.js b/packages/orchestration/src/exos/remote-chain-facade.js index 762d23c8f61..16a920124dd 100644 --- a/packages/orchestration/src/exos/remote-chain-facade.js +++ b/packages/orchestration/src/exos/remote-chain-facade.js @@ -7,13 +7,14 @@ import { VowShape } from '@agoric/vow'; import { ChainAddressShape, ChainFacadeI } from '../typeGuards.js'; /** + * @import {HostInterface, HostOf} from '@agoric/async-flow'; * @import {Zone} from '@agoric/base-zone'; * @import {TimerService} from '@agoric/time'; * @import {Remote} from '@agoric/internal'; * @import {Vow, VowTools} from '@agoric/vow'; * @import {CosmosInterchainService} from './cosmos-interchain-service.js'; * @import {prepareCosmosOrchestrationAccount} from './cosmos-orchestration-account.js'; - * @import {ChainInfo, CosmosChainInfo, IBCConnectionInfo, OrchestrationAccount, ChainAddress, IcaAccount, PromiseToVow, Denom} from '../types.js'; + * @import {ChainInfo, CosmosChainInfo, IBCConnectionInfo, OrchestrationAccount, ChainAddress, IcaAccount, PromiseToVow, Denom, Chain} from '../types.js'; */ const { Fail } = assert; @@ -83,30 +84,27 @@ const prepareRemoteChainFacadeKit = ( }, { public: { + /** @type {HostOf} */ getChainInfo() { return watch(this.state.remoteChainInfo); }, - /** @returns {Vow>>} */ + /** @type {HostOf} */ makeAccount() { - // TODO #9449 fix types - // @ts-expect-error Type 'Vow' is not assignable to type 'Vow' - return asVow(() => { - const { remoteChainInfo, connectionInfo } = this.state; - const stakingDenom = remoteChainInfo.stakingTokens?.[0]?.denom; - if (!stakingDenom) { - throw Fail`chain info lacks staking denom`; - } + const { remoteChainInfo, connectionInfo } = this.state; + const stakingDenom = remoteChainInfo.stakingTokens?.[0]?.denom; + if (!stakingDenom) { + return asVow(Fail`chain info lacks staking denom`); + } - return watch( - E(orchestration).makeAccount( - remoteChainInfo.chainId, - connectionInfo.id, - connectionInfo.counterparty.connection_id, - ), - this.facets.makeAccountWatcher, - ); - }); + return watch( + E(orchestration).makeAccount( + remoteChainInfo.chainId, + connectionInfo.id, + connectionInfo.counterparty.connection_id, + ), + this.facets.makeAccountWatcher, + ); }, }, makeAccountWatcher: { From df5d174a82e966d2305eae3a68908e8bf982f10b Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Wed, 10 Jul 2024 15:45:26 -0700 Subject: [PATCH 08/20] chore(types): rm Orchestration membrane helpers now in packages/async-flow --- .../src/exos/remote-chain-facade.js | 2 +- packages/orchestration/src/internal.ts | 20 ------------------- packages/orchestration/src/types.ts | 1 - packages/orchestration/test/types.test-d.ts | 16 +++++++-------- 4 files changed, 8 insertions(+), 31 deletions(-) delete mode 100644 packages/orchestration/src/internal.ts diff --git a/packages/orchestration/src/exos/remote-chain-facade.js b/packages/orchestration/src/exos/remote-chain-facade.js index 16a920124dd..acb8b008ed7 100644 --- a/packages/orchestration/src/exos/remote-chain-facade.js +++ b/packages/orchestration/src/exos/remote-chain-facade.js @@ -14,7 +14,7 @@ import { ChainAddressShape, ChainFacadeI } from '../typeGuards.js'; * @import {Vow, VowTools} from '@agoric/vow'; * @import {CosmosInterchainService} from './cosmos-interchain-service.js'; * @import {prepareCosmosOrchestrationAccount} from './cosmos-orchestration-account.js'; - * @import {ChainInfo, CosmosChainInfo, IBCConnectionInfo, OrchestrationAccount, ChainAddress, IcaAccount, PromiseToVow, Denom, Chain} from '../types.js'; + * @import {ChainInfo, CosmosChainInfo, IBCConnectionInfo, OrchestrationAccount, ChainAddress, IcaAccount, Denom, Chain} from '../types.js'; */ const { Fail } = assert; diff --git a/packages/orchestration/src/internal.ts b/packages/orchestration/src/internal.ts deleted file mode 100644 index e9960c97f5b..00000000000 --- a/packages/orchestration/src/internal.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { Vow } from '@agoric/vow'; - -/** - * Converts a function type that returns a Promise to a function type that - * returns a Vow. If the input is not a function returning a Promise, it - * preserves the original type. - * - * @template T - The type to transform - */ -export type PromiseToVow = T extends ( - ...args: infer Args -) => Promise - ? (...args: Args) => Vow - : T extends (...args: infer Args) => infer R - ? (...args: Args) => R - : T; - -export type VowifyAll = { - [K in keyof T]: PromiseToVow; -}; diff --git a/packages/orchestration/src/types.ts b/packages/orchestration/src/types.ts index 6ee257b930e..482820c7a66 100644 --- a/packages/orchestration/src/types.ts +++ b/packages/orchestration/src/types.ts @@ -5,7 +5,6 @@ export type * from './cosmos-api.js'; export type * from './ethereum-api.js'; export type * from './exos/chain-account-kit.js'; export type * from './exos/icq-connection-kit.js'; -export type * from './internal.js'; export type * from './orchestration-api.js'; export type * from './exos/cosmos-interchain-service.js'; export type * from './vat-orchestration.js'; diff --git a/packages/orchestration/test/types.test-d.ts b/packages/orchestration/test/types.test-d.ts index 93111f0b675..323b382cadf 100644 --- a/packages/orchestration/test/types.test-d.ts +++ b/packages/orchestration/test/types.test-d.ts @@ -6,6 +6,7 @@ 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 { GuestAsyncFunc, HostInterface, HostOf } from '@agoric/async-flow'; import type { ChainAddress, CosmosValidatorAddress, @@ -14,7 +15,6 @@ import type { } 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 { PromiseToVow, VowifyAll } from '../src/internal.js'; const anyVal = null as any; @@ -69,16 +69,16 @@ expectNotType(chainAddr); anyVal, anyVal, anyVal, - ) satisfies VowifyAll; + ) satisfies HostInterface; } -// PromiseToVow +// HostOf { type PromiseFn = () => Promise; type SyncFn = () => number; - type VowFn = PromiseToVow; - type StillSyncFn = PromiseToVow; + type VowFn = HostOf; + type StillSyncFn = HostOf; // Use type assertion instead of casting const vowFn: VowFn = (() => ({}) as Vow) as VowFn; @@ -93,9 +93,7 @@ expectNotType(chainAddr); // PromiseToVow with TransferSteps { - type TransferStepsVow = PromiseToVow< - OrchestrationAccount['transferSteps'] - >; + type TransferStepsVow = HostOf['transferSteps']>; const transferStepsVow: TransferStepsVow = (...args: any[]): Vow => ({}) as any; @@ -110,7 +108,7 @@ expectNotType(chainAddr); bizz: () => Record; }; - type VowObject = VowifyAll; + type VowObject = HostInterface; const vowObject: VowObject = { foo: () => ({}) as Vow, From 9e8a29a19d4c51c7294fe31f273f2358d222ee0c Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Wed, 10 Jul 2024 15:53:09 -0700 Subject: [PATCH 09/20] fix: use heapVowE for call on remote possible vow --- .../src/exos/local-chain-facade.js | 17 ++++++++--------- packages/orchestration/src/exos/orchestrator.js | 2 -- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/packages/orchestration/src/exos/local-chain-facade.js b/packages/orchestration/src/exos/local-chain-facade.js index 79902b9594a..8770dbb741a 100644 --- a/packages/orchestration/src/exos/local-chain-facade.js +++ b/packages/orchestration/src/exos/local-chain-facade.js @@ -1,5 +1,7 @@ /** @file ChainAccount exo */ import { E } from '@endo/far'; +// eslint-disable-next-line no-restricted-syntax -- just the import +import { heapVowE } from '@agoric/vow/vat.js'; import { M } from '@endo/patterns'; import { pickFacet } from '@agoric/vat-data'; import { VowShape } from '@agoric/vow'; @@ -13,7 +15,7 @@ import { ChainFacadeI } from '../typeGuards.js'; * @import {LocalChain, LocalChainAccount} from '@agoric/vats/src/localchain.js'; * @import {Vow, VowTools} from '@agoric/vow'; * @import {CosmosInterchainService} from './cosmos-interchain-service.js'; - * @import {MakeLocalOrchestrationAccountKit} from './local-orchestration-account.js'; + * @import {LocalOrchestrationAccountKit, MakeLocalOrchestrationAccountKit} from './local-orchestration-account.js'; * @import {ChainAddress, ChainInfo, CosmosChainInfo, IBCConnectionInfo, OrchestrationAccount} from '../types.js'; */ @@ -70,17 +72,14 @@ const prepareLocalChainFacadeKit = ( return watch(this.state.localChainInfo); }, - // FIXME parameterize on the remoteChainInfo to make() - // That used to work but got lost in the migration to Exo - /** @returns {Vow>} */ + /** @returns {Vow} */ makeAccount() { const lcaP = E(localchain).makeAccount(); - // TODO #9449 fix types - // @ts-expect-error Type 'Vow' is not assignable to type 'Vow'. return watch( - // TODO #9449 fix types - // @ts-expect-error Property 'getAddress' does not exist on type 'EMethods} */ getChain(name) { if (name === 'agoric') { - // TODO #9449 fix types - // @ts-expect-error Type 'Vow' is not assignable to type 'Vow>'. return watch( chainHub.getChainInfo('agoric'), this.facets.makeLocalChainFacadeWatcher, From 704bad6e7e38e942fb043f2a65b57c8bb67d1ac4 Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Wed, 10 Jul 2024 15:56:44 -0700 Subject: [PATCH 10/20] chore(types): produce stakeOsmo --- packages/orchestration/src/proposals/start-stakeOsmo.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/orchestration/src/proposals/start-stakeOsmo.js b/packages/orchestration/src/proposals/start-stakeOsmo.js index 6fcfde82d5a..4efaf74e3bc 100644 --- a/packages/orchestration/src/proposals/start-stakeOsmo.js +++ b/packages/orchestration/src/proposals/start-stakeOsmo.js @@ -21,6 +21,11 @@ const trace = makeTracer('StartStakeOsmo', true); * >; * }; * }; + * instance: { + * produce: { + * stakeOsmo: any; + * }; + * }; * }} powers */ export const startStakeOsmo = async ({ @@ -36,7 +41,6 @@ export const startStakeOsmo = async ({ consume: { stakeIca }, }, instance: { - // @ts-expect-error stakeOsmo not typed produce: { stakeOsmo: produceInstance }, }, }) => { From b4fdee66e17c75c6055283cd1d6675fd6dff3f19 Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Wed, 10 Jul 2024 17:01:55 -0700 Subject: [PATCH 11/20] feat(types): LocalAccountMethods for agoric Orch account --- packages/orchestration/src/orchestration-api.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/orchestration/src/orchestration-api.ts b/packages/orchestration/src/orchestration-api.ts index 5d6da28a9ad..3847f7d5356 100644 --- a/packages/orchestration/src/orchestration-api.ts +++ b/packages/orchestration/src/orchestration-api.ts @@ -15,7 +15,9 @@ import type { CosmosChainAccountMethods, CosmosChainInfo, IBCMsgTransferOptions, + IcaAccount, KnownChains, + LocalAccountMethods, } from './types.js'; /** @@ -62,7 +64,11 @@ export type ChainAddress = { }; export type OrchestrationAccount = OrchestrationAccountI & - (CI extends CosmosChainInfo ? CosmosChainAccountMethods : never); + (CI extends CosmosChainInfo + ? CI['chainId'] extends `agoric${string}` + ? CosmosChainAccountMethods & LocalAccountMethods + : CosmosChainAccountMethods + : {}); /** * An object for access the core functions of a remote chain. From 96b5c6e28722dab4e47399ba902e409be19c99a3 Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Wed, 10 Jul 2024 16:01:54 -0700 Subject: [PATCH 12/20] chore(types): LocalTransfer --- .../src/examples/swapExample.contract.js | 6 +++--- packages/orchestration/src/utils/zoe-tools.js | 15 ++++++++++----- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/packages/orchestration/src/examples/swapExample.contract.js b/packages/orchestration/src/examples/swapExample.contract.js index 944f72be366..a7d4bb806b1 100644 --- a/packages/orchestration/src/examples/swapExample.contract.js +++ b/packages/orchestration/src/examples/swapExample.contract.js @@ -5,6 +5,8 @@ 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'; * @import {LocalChain} from '@agoric/vats/src/localchain.js'; @@ -18,7 +20,7 @@ import { withOrchestration } from '../utils/start-helper.js'; /** * @param {Orchestrator} orch * @param {object} ctx - * @param {OrchestrationTools['zoeTools']['localTransfer']} ctx.localTransfer + * @param {LocalTransfer} ctx.localTransfer * @param {ZCFSeat} seat * @param {object} offerArgs * @param {Amount<'nat'>} offerArgs.staked @@ -38,8 +40,6 @@ const stackAndSwapFn = async (orch, { localTransfer }, seat, offerArgs) => { const omniAddress = omniAccount.getAddress(); // deposit funds from user seat to LocalChainAccount - // TODO localTransfer type returns vow but in the guest context it should be a promise - // @ts-expect-error XXX localAccount type await localTransfer(seat, localAccount, give); seat.exit(); diff --git a/packages/orchestration/src/utils/zoe-tools.js b/packages/orchestration/src/utils/zoe-tools.js index 0f756fd1360..a94eccfb6d4 100644 --- a/packages/orchestration/src/utils/zoe-tools.js +++ b/packages/orchestration/src/utils/zoe-tools.js @@ -6,6 +6,15 @@ import { atomicTransfer } from '@agoric/zoe/src/contractSupport/index.js'; * @import {Vow, VowTools} from '@agoric/vow'; * @import {Zone} from '@agoric/zone'; * @import {OrchestrationAccount} from '../orchestration-api.js' + * @import {LocalAccountMethods} from '../types.js'; + */ + +/** + * @typedef {( + * srcSeat: ZCFSeat, + * localAccount: LocalAccountMethods, + * give: AmountKeywordRecord, + * ) => Promise} LocalTransfer */ /** @@ -20,11 +29,7 @@ export const makeZoeTools = (zone, { zcf, vowTools }) => { zone, 'localTransfer', /** - * @type {( - * srcSeat: ZCFSeat, - * localAccount: LocalOrchestrationAccountKit['holder'], - * give: AmountKeywordRecord, - * ) => Promise} + * @type {LocalTransfer} */ async (srcSeat, localAccount, give) => { !srcSeat.hasExited() || Fail`The seat cannot have exited.`; From 4ad9fb5f820871bd90b0ea2f70a610a5fe805ebe Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Wed, 10 Jul 2024 17:01:29 -0700 Subject: [PATCH 13/20] chore(types): annotate Orchestration account impls --- .../src/exos/cosmos-orchestration-account.js | 39 +++++++------------ .../src/exos/local-orchestration-account.js | 15 +++++-- .../test/examples/stake-ica.contract.test.ts | 2 +- 3 files changed, 26 insertions(+), 30 deletions(-) diff --git a/packages/orchestration/src/exos/cosmos-orchestration-account.js b/packages/orchestration/src/exos/cosmos-orchestration-account.js index fdb160e02de..8be638feccd 100644 --- a/packages/orchestration/src/exos/cosmos-orchestration-account.js +++ b/packages/orchestration/src/exos/cosmos-orchestration-account.js @@ -33,6 +33,7 @@ import { maxClockSkew, tryDecodeResponse } from '../utils/cosmos.js'; import { orchestrationAccountMethods } from '../utils/orchestrationAccount.js'; /** + * @import {HostOf} from '@agoric/async-flow'; * @import {AmountArg, IcaAccount, ChainAddress, CosmosValidatorAddress, ICQConnection, StakingAccountActions, DenomAmount, OrchestrationAccountI, DenomArg} from '../types.js'; * @import {RecorderKit, MakeRecorderKit} from '@agoric/zoe/src/contractSupport/recorder.js'; * @import {Coin} from '@agoric/cosmic-proto/cosmos/base/v1beta1/coin.js'; @@ -309,6 +310,7 @@ export const prepareCosmosOrchestrationAccountKit = ( }, }, holder: { + /** @type {HostOf} */ asContinuingOffer() { // getPublicTopics resolves promptly (same run), so we don't need a watcher // eslint-disable-next-line no-restricted-syntax @@ -325,6 +327,7 @@ export const prepareCosmosOrchestrationAccountKit = ( }); }); }, + /** @type {HostOf} */ getPublicTopics() { // getStoragePath resolves promptly (same run), so we don't need a watcher // eslint-disable-next-line no-restricted-syntax @@ -341,18 +344,11 @@ export const prepareCosmosOrchestrationAccountKit = ( }); }, - // TODO move this beneath the Orchestration abstraction, - // to the OrchestrationAccount provided by makeAccount() - /** @returns {ChainAddress} */ + /** @type {HostOf} */ getAddress() { return this.state.chainAddress; }, - /** - * _Assumes users has already sent funds to their ICA, until #9193 - * - * @param {CosmosValidatorAddress} validator - * @param {AmountArg} amount - */ + /** @type {HostOf} */ delegate(validator, amount) { return asVow(() => { trace('delegate', validator, amount); @@ -371,17 +367,12 @@ export const prepareCosmosOrchestrationAccountKit = ( return watch(results, this.facets.returnVoidWatcher); }); }, + /** @type {HostOf} */ getBalances() { // TODO https://github.com/Agoric/agoric-sdk/issues/9610 return asVow(() => Fail`not yet implemented`); }, - /** - * _Assumes users has already sent funds to their ICA, until #9193 - * - * @param {CosmosValidatorAddress} srcValidator - * @param {CosmosValidatorAddress} dstValidator - * @param {AmountArg} amount - */ + /** @type {HostOf} */ redelegate(srcValidator, dstValidator, amount) { return asVow(() => { trace('redelegate', srcValidator, dstValidator, amount); @@ -406,10 +397,7 @@ export const prepareCosmosOrchestrationAccountKit = ( ); }); }, - /** - * @param {CosmosValidatorAddress} validator - * @returns {Vow} - */ + /** @type {HostOf} */ withdrawReward(validator) { return asVow(() => { trace('withdrawReward', validator); @@ -425,10 +413,7 @@ export const prepareCosmosOrchestrationAccountKit = ( return watch(results, this.facets.withdrawRewardWatcher); }); }, - /** - * @param {DenomArg} denom - * @returns {Vow} - */ + /** @type {HostOf} */ getBalance(denom) { return asVow(() => { const { chainAddress, icqConnection } = this.state; @@ -450,26 +435,30 @@ export const prepareCosmosOrchestrationAccountKit = ( }); }, + /** @type {HostOf} */ send(toAccount, amount) { console.log('send got', toAccount, amount); return asVow(() => Fail`not yet implemented`); }, + /** @type {HostOf} */ transfer(amount, msg) { console.log('transferSteps got', amount, msg); return asVow(() => Fail`not yet implemented`); }, + /** @type {HostOf} */ transferSteps(amount, msg) { console.log('transferSteps got', amount, msg); return asVow(() => Fail`not yet implemented`); }, + /** @type {HostOf} */ withdrawRewards() { return asVow(() => Fail`Not Implemented. Try using withdrawReward.`); }, - /** @param {Omit[]} delegations */ + /** @type {HostOf} */ undelegate(delegations) { return asVow(() => { trace('undelegate', delegations); diff --git a/packages/orchestration/src/exos/local-orchestration-account.js b/packages/orchestration/src/exos/local-orchestration-account.js index bdc7ad63404..720724aabac 100644 --- a/packages/orchestration/src/exos/local-orchestration-account.js +++ b/packages/orchestration/src/exos/local-orchestration-account.js @@ -21,7 +21,7 @@ import { makeTimestampHelper } from '../utils/time.js'; /** * @import {HostOf} from '@agoric/async-flow'; * @import {LocalChainAccount} from '@agoric/vats/src/localchain.js'; - * @import {AmountArg, ChainAddress, DenomAmount, IBCMsgTransferOptions, OrchestrationAccount, ChainInfo, IBCConnectionInfo} from '@agoric/orchestration'; + * @import {AmountArg, ChainAddress, DenomAmount, IBCMsgTransferOptions, OrchestrationAccount, ChainInfo, IBCConnectionInfo, OrchestrationAccountI} from '@agoric/orchestration'; * @import {RecorderKit, MakeRecorderKit} from '@agoric/zoe/src/contractSupport/recorder.js'. * @import {Zone} from '@agoric/zone'; * @import {Remote} from '@agoric/internal'; @@ -288,6 +288,7 @@ export const prepareLocalOrchestrationAccountKit = ( }, }, holder: { + /** @type {HostOf} */ asContinuingOffer() { // getPublicTopics resolves promptly (same run), so we don't need a watcher // eslint-disable-next-line no-restricted-syntax @@ -307,7 +308,7 @@ export const prepareLocalOrchestrationAccountKit = ( /** * TODO: balance lookups for non-vbank assets * - * @type {HostOf['getBalance']>} + * @type {HostOf} */ getBalance(denomArg) { // FIXME look up real values @@ -323,11 +324,15 @@ export const prepareLocalOrchestrationAccountKit = ( denom, ); }, + /** @type {HostOf} */ getBalances() { // TODO https://github.com/Agoric/agoric-sdk/issues/9610 return asVow(() => Fail`not yet implemented`); }, + /** + * @type {HostOf} + */ getPublicTopics() { // getStoragePath resolves promptly (same run), so we don't need a watcher // eslint-disable-next-line no-restricted-syntax @@ -343,6 +348,7 @@ export const prepareLocalOrchestrationAccountKit = ( }); }); }, + // FIXME take ChainAddress to match OrchestrationAccountI /** * @param {string} validatorAddress * @param {Amount<'nat'>} ertpAmount @@ -366,6 +372,7 @@ export const prepareLocalOrchestrationAccountKit = ( this.facets.extractFirstResultWatcher, ); }, + // FIXME take ChainAddress to match OrchestrationAccountI /** * @param {string} validatorAddress * @param {Amount<'nat'>} ertpAmount @@ -409,7 +416,7 @@ export const prepareLocalOrchestrationAccountKit = ( executeTx(messages) { return watch(E(this.state.account).executeTx(messages)); }, - /** @type {OrchestrationAccount['getAddress']} */ + /** @type {OrchestrationAccountI['getAddress']} */ getAddress() { return this.state.address; }, @@ -457,7 +464,7 @@ export const prepareLocalOrchestrationAccountKit = ( return watch(transferV, this.facets.returnVoidWatcher); }); }, - /** @type {HostOf['transferSteps']>} */ + /** @type {HostOf} */ transferSteps(amount, msg) { return asVow(() => { console.log('transferSteps got', amount, msg); diff --git a/packages/orchestration/test/examples/stake-ica.contract.test.ts b/packages/orchestration/test/examples/stake-ica.contract.test.ts index 4ce9cf04df5..195ffe42e57 100644 --- a/packages/orchestration/test/examples/stake-ica.contract.test.ts +++ b/packages/orchestration/test/examples/stake-ica.contract.test.ts @@ -253,7 +253,7 @@ test('CosmosOrchestrationAccount - not yet implemented', async t => { await t.throwsAsync(E(account).transfer(mockAmountArg, mockChainAddress), { message: 'not yet implemented', }); - await t.throwsAsync(E(account).transferSteps(mockAmountArg, []), { + await t.throwsAsync(E(account).transferSteps(mockAmountArg, null as any), { message: 'not yet implemented', }); await t.throwsAsync(E(account).withdrawRewards(), { From beeb7daae373933572adfb2c83cea900fd0e11a7 Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Wed, 10 Jul 2024 17:21:14 -0700 Subject: [PATCH 14/20] chore(types): offerWatcher --- packages/smart-wallet/src/offerWatcher.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/smart-wallet/src/offerWatcher.js b/packages/smart-wallet/src/offerWatcher.js index e71d53960b4..4aace001224 100644 --- a/packages/smart-wallet/src/offerWatcher.js +++ b/packages/smart-wallet/src/offerWatcher.js @@ -128,9 +128,10 @@ export const prepareOfferWatcher = (baggage, vowTools) => { baggage, 'OfferWatcher', offerWatcherGuard, + // XXX walletHelper is `any` because the helper facet is too nested to export its type /** * @param {any} walletHelper - * @param {any} deposit + * @param {{ receive: (payment: Payment) => Promise }} deposit * @param {OfferSpec} offerSpec * @param {string} address * @param {Amount<'set'>} invitationAmount From 91ef3523109754c88fd051d3b9777e5cc71239e3 Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Wed, 10 Jul 2024 17:52:52 -0700 Subject: [PATCH 15/20] fix(types): PublicSubscribers --- packages/smart-wallet/src/types.d.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/smart-wallet/src/types.d.ts b/packages/smart-wallet/src/types.d.ts index 4e780451590..6e6875a6818 100644 --- a/packages/smart-wallet/src/types.d.ts +++ b/packages/smart-wallet/src/types.d.ts @@ -10,6 +10,7 @@ import type { CapData } from '@endo/marshal'; import type { agoric } from '@agoric/cosmic-proto'; import type { AgoricNamesRemotes } from '@agoric/vats/tools/board-utils.js'; import type { StoredFacet } from '@agoric/internal/src/lib-chainStorage.js'; +import type { PublicTopic } from '@agoric/zoe/src/contractSupport/topics.js'; import type { OfferSpec } from './offers.js'; declare const CapDataShape: unique symbol; @@ -27,7 +28,7 @@ export type InvitationMakers = Record< (...args: any[]) => Promise >; -export type PublicSubscribers = Record>; +export type PublicSubscribers = Record; export interface ContinuingOfferResult { invitationMakers: InvitationMakers; From 4be48424ffeb42c56274084246c951e004245611 Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Wed, 10 Jul 2024 17:53:07 -0700 Subject: [PATCH 16/20] chore(types): Orchestration continuing offers --- .../src/exos/cosmos-orchestration-account.js | 12 +++++++++--- .../src/exos/local-orchestration-account.js | 12 +++++++++--- packages/orchestration/src/orchestration-api.ts | 14 +++++++------- packages/orchestration/src/utils/zoe-tools.js | 12 +++++++++++- packages/zoe/src/contractSupport/topics.js | 13 +++++++++++++ 5 files changed, 49 insertions(+), 14 deletions(-) diff --git a/packages/orchestration/src/exos/cosmos-orchestration-account.js b/packages/orchestration/src/exos/cosmos-orchestration-account.js index 8be638feccd..f3d299b0cdb 100644 --- a/packages/orchestration/src/exos/cosmos-orchestration-account.js +++ b/packages/orchestration/src/exos/cosmos-orchestration-account.js @@ -39,11 +39,13 @@ import { orchestrationAccountMethods } from '../utils/orchestrationAccount.js'; * @import {Coin} from '@agoric/cosmic-proto/cosmos/base/v1beta1/coin.js'; * @import {Delegation} from '@agoric/cosmic-proto/cosmos/staking/v1beta1/staking.js'; * @import {Remote} from '@agoric/internal'; + * @import {InvitationMakers} from '@agoric/smart-wallet/src/types.js'; * @import {TimerService} from '@agoric/time'; * @import {Vow, VowTools} from '@agoric/vow'; * @import {Zone} from '@agoric/zone'; * @import {ResponseQuery} from '@agoric/cosmic-proto/tendermint/abci/types.js'; * @import {JsonSafe} from '@agoric/cosmic-proto'; + * @import {Matcher} from '@endo/patterns'; */ const trace = makeTracer('ComosOrchestrationAccountHolder'); @@ -82,7 +84,7 @@ export const IcaAccountHolderI = M.interface('IcaAccountHolder', { undelegate: M.call(M.arrayOf(DelegationShape)).returns(VowShape), }); -/** @type {{ [name: string]: [description: string, valueShape: Pattern] }} */ +/** @type {{ [name: string]: [description: string, valueShape: Matcher] }} */ const PUBLIC_TOPICS = { account: ['Staking Account holder status', M.any()], }; @@ -159,7 +161,6 @@ export const prepareCosmosOrchestrationAccountKit = ( (chainAddress, bondDenom, io) => { const { storageNode, ...rest } = io; // must be the fully synchronous maker because the kit is held in durable state - // @ts-expect-error XXX Patterns const topicKit = makeRecorderKit(storageNode, PUBLIC_TOPICS.account[1]); // TODO determine what goes in vstorage https://github.com/Agoric/agoric-sdk/issues/9066 void E(topicKit.recorder).write(''); @@ -316,7 +317,12 @@ export const prepareCosmosOrchestrationAccountKit = ( // eslint-disable-next-line no-restricted-syntax return asVow(async () => { await null; - const { holder, invitationMakers } = this.facets; + const { holder, invitationMakers: im } = this.facets; + // XXX cast to a type that has string index signature + const invitationMakers = /** @type {InvitationMakers} */ ( + /** @type {unknown} */ (im) + ); + return harden({ // getPublicTopics returns a vow, for membrane compatibility. // it's safe to unwrap to a promise and get the result as we diff --git a/packages/orchestration/src/exos/local-orchestration-account.js b/packages/orchestration/src/exos/local-orchestration-account.js index 720724aabac..c5d168189be 100644 --- a/packages/orchestration/src/exos/local-orchestration-account.js +++ b/packages/orchestration/src/exos/local-orchestration-account.js @@ -25,9 +25,11 @@ import { makeTimestampHelper } from '../utils/time.js'; * @import {RecorderKit, MakeRecorderKit} from '@agoric/zoe/src/contractSupport/recorder.js'. * @import {Zone} from '@agoric/zone'; * @import {Remote} from '@agoric/internal'; + * @import {InvitationMakers} from '@agoric/smart-wallet/src/types.js'; * @import {TimerService, TimerBrand, TimestampRecord} from '@agoric/time'; * @import {PromiseVow, Vow, VowTools} from '@agoric/vow'; * @import {TypedJson, JsonSafe} from '@agoric/cosmic-proto'; + * @import {Matcher} from '@endo/patterns'; * @import {ChainHub} from './chain-hub.js'; */ @@ -58,7 +60,7 @@ const HolderI = M.interface('holder', { executeTx: M.call(M.arrayOf(M.record())).returns(Vow$(M.record())), }); -/** @type {{ [name: string]: [description: string, valueShape: Pattern] }} */ +/** @type {{ [name: string]: [description: string, valueShape: Matcher] }} */ const PUBLIC_TOPICS = { account: ['Account holder status', M.any()], }; @@ -137,7 +139,6 @@ export const prepareLocalOrchestrationAccountKit = ( */ ({ account, address, storageNode }) => { // must be the fully synchronous maker because the kit is held in durable state - // @ts-expect-error XXX Patterns const topicKit = makeRecorderKit(storageNode, PUBLIC_TOPICS.account[1]); // TODO determine what goes in vstorage https://github.com/Agoric/agoric-sdk/issues/9066 void E(topicKit.recorder).write(''); @@ -294,7 +295,12 @@ export const prepareLocalOrchestrationAccountKit = ( // eslint-disable-next-line no-restricted-syntax return asVow(async () => { await null; - const { holder, invitationMakers } = this.facets; + const { holder, invitationMakers: im } = this.facets; + // XXX cast to a type that has string index signature + const invitationMakers = /** @type {InvitationMakers} */ ( + /** @type {unknown} */ (im) + ); + return harden({ // getPublicTopics returns a vow, for membrane compatibility. // it's safe to unwrap to a promise and get the result as we diff --git a/packages/orchestration/src/orchestration-api.ts b/packages/orchestration/src/orchestration-api.ts index 3847f7d5356..4c7998857a7 100644 --- a/packages/orchestration/src/orchestration-api.ts +++ b/packages/orchestration/src/orchestration-api.ts @@ -5,20 +5,19 @@ * - should remain relatively stable. */ import type { Amount, Brand, NatAmount } from '@agoric/ertp/src/types.js'; -import type { LocalChainAccount } from '@agoric/vats/src/localchain.js'; -import type { Timestamp } from '@agoric/time'; -import type { ContinuingOfferResult } from '@agoric/smart-wallet/src/types.js'; -import type { TopicsRecord } from '@agoric/zoe/src/contractSupport/topics.js'; import type { CurrentWalletRecord } from '@agoric/smart-wallet/src/smartWallet.js'; +import type { Timestamp } from '@agoric/time'; +import type { LocalChainAccount } from '@agoric/vats/src/localchain.js'; +import type { ResolvedPublicTopic } from '@agoric/zoe/src/contractSupport/topics.js'; import type { ChainInfo, CosmosChainAccountMethods, CosmosChainInfo, IBCMsgTransferOptions, - IcaAccount, KnownChains, LocalAccountMethods, } from './types.js'; +import type { ResolvedContinuingOfferResult } from './utils/zoe-tools.js'; /** * A denom that designates a path to a token type on some blockchain. @@ -182,14 +181,15 @@ export interface OrchestrationAccountI { * holder's smart wallet so they can continue interacting with the account * and read account state in vstorage if published. */ - asContinuingOffer: () => Promise; + asContinuingOffer: () => Promise; + /** * Public topics are a map to different vstorage paths and subscribers that * can be shared with on or offchain clients. * When returned as part of a continuing invitation, it will appear * in the {@link CurrentWalletRecord} in vstorage. */ - getPublicTopics: () => Promise; + getPublicTopics: () => Promise>>; } /** diff --git a/packages/orchestration/src/utils/zoe-tools.js b/packages/orchestration/src/utils/zoe-tools.js index a94eccfb6d4..4fe36052894 100644 --- a/packages/orchestration/src/utils/zoe-tools.js +++ b/packages/orchestration/src/utils/zoe-tools.js @@ -2,13 +2,23 @@ import { Fail } from '@endo/errors'; import { atomicTransfer } from '@agoric/zoe/src/contractSupport/index.js'; /** - * @import {LocalOrchestrationAccountKit} from '../exos/local-orchestration-account.js'; + * @import {InvitationMakers} from '@agoric/smart-wallet/src/types.js'; + * @import {ResolvedPublicTopic} from '@agoric/zoe/src/contractSupport/topics.js'; * @import {Vow, VowTools} from '@agoric/vow'; * @import {Zone} from '@agoric/zone'; * @import {OrchestrationAccount} from '../orchestration-api.js' * @import {LocalAccountMethods} from '../types.js'; */ +/** + * @typedef {{ + * invitationMakers: InvitationMakers; + * publicSubscribers: Record>; + * }} ResolvedContinuingOfferResult + * + * @see {ContinuingOfferResult} + */ + /** * @typedef {( * srcSeat: ZCFSeat, diff --git a/packages/zoe/src/contractSupport/topics.js b/packages/zoe/src/contractSupport/topics.js index 8f1f15283e1..a0f9ce226d9 100644 --- a/packages/zoe/src/contractSupport/topics.js +++ b/packages/zoe/src/contractSupport/topics.js @@ -21,6 +21,19 @@ export const PublicTopicShape = M.splitRecord( * }} PublicTopic */ +/** + * A {PublicTopic} in which the `storagePath` is always a resolved string. + * + * Useful when working with Vows and async-flow. + * + * @template {object} T topic value + * @typedef {{ + * description?: string, + * subscriber: Subscriber, + * storagePath: string, + * }} ResolvedPublicTopic + */ + export const TopicsRecordShape = M.recordOf(M.string(), PublicTopicShape); /** From 43a8f5ad4d553ca0934377dc27606f219274373c Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Wed, 10 Jul 2024 17:54:19 -0700 Subject: [PATCH 17/20] chore: update type-coverage --- packages/orchestration/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/orchestration/package.json b/packages/orchestration/package.json index 0dafc15bdf1..37bcc1834d3 100644 --- a/packages/orchestration/package.json +++ b/packages/orchestration/package.json @@ -89,6 +89,6 @@ "access": "public" }, "typeCoverage": { - "atLeast": 97.89 + "atLeast": 97.99 } } From 2405c8f779acbfe9defd539c6a79a46f8891a798 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Tue, 25 Jun 2024 15:20:45 -0500 Subject: [PATCH 18/20] chore(facade): say _which_ params are missing --- packages/orchestration/src/facade.js | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/orchestration/src/facade.js b/packages/orchestration/src/facade.js index 667d7fa8290..b98000c214a 100644 --- a/packages/orchestration/src/facade.js +++ b/packages/orchestration/src/facade.js @@ -1,6 +1,6 @@ /** @file Orchestration facade */ -import { Fail } from '@endo/errors'; +import { assertAllDefined } from '@agoric/internal'; /** * @import {AsyncFlowTools} from '@agoric/async-flow'; @@ -38,17 +38,17 @@ export const makeOrchestrationFacade = ({ vowTools, asyncFlowTools, }) => { - (zone && - timerService && - zcf && - storageNode && - orchestrationService && - // @ts-expect-error type says defined but double check - makeRecorderKit && - // @ts-expect-error type says defined but double check - makeOrchestrator && - asyncFlowTools) || - Fail`params missing`; + assertAllDefined({ + zone, + timerService, + zcf, + storageNode, + orchestrationService, + makeRecorderKit, + makeOrchestrator, + vowTools, + asyncFlowTools, + }); const { prepareEndowment, asyncFlow, adminAsyncFlow } = asyncFlowTools; From f10c9c1627dcd5baf0f11165f28518ad20a90e93 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Tue, 11 Jun 2024 17:58:37 -0500 Subject: [PATCH 19/20] chore(facade): harden API surfaces --- packages/orchestration/src/facade.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/orchestration/src/facade.js b/packages/orchestration/src/facade.js index b98000c214a..ae6b4cf007a 100644 --- a/packages/orchestration/src/facade.js +++ b/packages/orchestration/src/facade.js @@ -109,11 +109,11 @@ export const makeOrchestrationFacade = ({ ]), ); - return { + return harden({ adminAsyncFlow, orchestrate, orchestrateAll, - }; + }); }; harden(makeOrchestrationFacade); /** @typedef {ReturnType} OrchestrationFacade */ From 6b4182963cbc87b6eb22b77833e420fa35bb2da6 Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Thu, 11 Jul 2024 13:49:40 -0700 Subject: [PATCH 20/20] fix(types): TypedMatcher --> TypedPattern --- .../inter-protocol/src/auction/auctionBook.js | 2 +- .../inter-protocol/src/auction/auctioneer.js | 2 +- .../src/price/fluxAggregatorKit.js | 4 ++-- packages/inter-protocol/src/psm/psm.js | 2 +- .../src/reserve/assetReserveKit.js | 2 +- .../src/vaultFactory/vaultDirector.js | 2 +- packages/zoe/src/contractSupport/recorder.js | 17 +++++++---------- 7 files changed, 14 insertions(+), 17 deletions(-) diff --git a/packages/inter-protocol/src/auction/auctionBook.js b/packages/inter-protocol/src/auction/auctionBook.js index 3b373c79a79..be91b29a370 100644 --- a/packages/inter-protocol/src/auction/auctionBook.js +++ b/packages/inter-protocol/src/auction/auctionBook.js @@ -172,7 +172,7 @@ export const prepareAuctionBook = (baggage, zcf, makeRecorderKit) => { const bookDataKit = makeRecorderKit( node, - /** @type {import('@agoric/zoe/src/contractSupport/recorder.js').TypedMatcher} */ ( + /** @type {import('@agoric/zoe/src/contractSupport/recorder.js').TypedPattern} */ ( M.any() ), ); diff --git a/packages/inter-protocol/src/auction/auctioneer.js b/packages/inter-protocol/src/auction/auctioneer.js index 6d6b2d021a1..b2d480ab3dd 100644 --- a/packages/inter-protocol/src/auction/auctioneer.js +++ b/packages/inter-protocol/src/auction/auctioneer.js @@ -440,7 +440,7 @@ export const start = async (zcf, privateArgs, baggage) => { const scheduleKit = makeERecorderKit( E(privateArgs.storageNode).makeChildNode('schedule'), /** - * @type {import('@agoric/zoe/src/contractSupport/recorder.js').TypedMatcher< + * @type {import('@agoric/zoe/src/contractSupport/recorder.js').TypedPattern< * import('./scheduler.js').ScheduleNotification * >} */ (M.any()), diff --git a/packages/inter-protocol/src/price/fluxAggregatorKit.js b/packages/inter-protocol/src/price/fluxAggregatorKit.js index 164a5997da6..70b3e1d58ed 100644 --- a/packages/inter-protocol/src/price/fluxAggregatorKit.js +++ b/packages/inter-protocol/src/price/fluxAggregatorKit.js @@ -144,7 +144,7 @@ export const prepareFluxAggregatorKit = async ( priceKit: () => makeRecorderKit( storageNode, - /** @type {import('@agoric/zoe/src/contractSupport/recorder.js').TypedMatcher} */ ( + /** @type {import('@agoric/zoe/src/contractSupport/recorder.js').TypedPattern} */ ( M.any() ), ), @@ -153,7 +153,7 @@ export const prepareFluxAggregatorKit = async ( makeRecorderKit( node, /** - * @type {import('@agoric/zoe/src/contractSupport/recorder.js').TypedMatcher< + * @type {import('@agoric/zoe/src/contractSupport/recorder.js').TypedPattern< * import('./roundsManager.js').LatestRound * >} */ (M.any()), diff --git a/packages/inter-protocol/src/psm/psm.js b/packages/inter-protocol/src/psm/psm.js index f19e1cb9fe2..113a6f7ab6e 100644 --- a/packages/inter-protocol/src/psm/psm.js +++ b/packages/inter-protocol/src/psm/psm.js @@ -174,7 +174,7 @@ export const start = async (zcf, privateArgs, baggage) => { E.when(E(privateArgs.storageNode).makeChildNode('metrics'), node => makeRecorderKit( node, - /** @type {import('@agoric/zoe/src/contractSupport/recorder.js').TypedMatcher} */ ( + /** @type {import('@agoric/zoe/src/contractSupport/recorder.js').TypedPattern} */ ( M.any() ), ), diff --git a/packages/inter-protocol/src/reserve/assetReserveKit.js b/packages/inter-protocol/src/reserve/assetReserveKit.js index b21da11e2fe..673901e46de 100644 --- a/packages/inter-protocol/src/reserve/assetReserveKit.js +++ b/packages/inter-protocol/src/reserve/assetReserveKit.js @@ -88,7 +88,7 @@ export const prepareAssetReserveKit = async ( keywordForBrand, metricsKit: makeRecorderKit( metricsNode, - /** @type {import('@agoric/zoe/src/contractSupport/recorder.js').TypedMatcher} */ ( + /** @type {import('@agoric/zoe/src/contractSupport/recorder.js').TypedPattern} */ ( M.any() ), ), diff --git a/packages/inter-protocol/src/vaultFactory/vaultDirector.js b/packages/inter-protocol/src/vaultFactory/vaultDirector.js index 5eea587cd44..cb221c938b3 100644 --- a/packages/inter-protocol/src/vaultFactory/vaultDirector.js +++ b/packages/inter-protocol/src/vaultFactory/vaultDirector.js @@ -131,7 +131,7 @@ const prepareVaultDirector = ( const metricsKit = makeERecorderKit( metricsNode, - /** @type {import('@agoric/zoe/src/contractSupport/recorder.js').TypedMatcher} */ ( + /** @type {import('@agoric/zoe/src/contractSupport/recorder.js').TypedPattern} */ ( M.any() ), ); diff --git a/packages/zoe/src/contractSupport/recorder.js b/packages/zoe/src/contractSupport/recorder.js index 65c0d7d324d..a0c81562400 100644 --- a/packages/zoe/src/contractSupport/recorder.js +++ b/packages/zoe/src/contractSupport/recorder.js @@ -56,12 +56,12 @@ export const prepareRecorder = (baggage, marshaller) => { * @template T * @param {PublishKit['publisher']} publisher * @param {Awaited>} storageNode - * @param {TypedMatcher} [valueShape] + * @param {TypedPattern} [valueShape] */ ( publisher, storageNode, - valueShape = /** @type {TypedMatcher} */ (M.any()), + valueShape = /** @type {TypedPattern} */ (M.any()), ) => { return { closed: false, @@ -145,7 +145,7 @@ export const defineRecorderKit = ({ makeRecorder, makeDurablePublishKit }) => { /** * @template T * @param {StorageNode | Awaited>} storageNode - * @param {TypedMatcher} [valueShape] + * @param {TypedPattern} [valueShape] * @returns {RecorderKit} */ const makeRecorderKit = (storageNode, valueShape) => { @@ -174,7 +174,7 @@ export const defineERecorderKit = ({ makeRecorder, makeDurablePublishKit }) => { /** * @template T * @param {ERef} storageNodeP - * @param {TypedMatcher} [valueShape] + * @param {TypedPattern} [valueShape] * @returns {EventualRecorderKit} */ const makeERecorderKit = (storageNodeP, valueShape) => { @@ -263,14 +263,11 @@ export const prepareMockRecorderKitMakers = () => { * Stop-gap until https://github.com/Agoric/agoric-sdk/issues/6160 * explictly specify the type that the Pattern will verify through a match. * - * This is a Pattern but since that's `any`, including in the typedef turns the - * whole thing to `any`. - * * @template T - * @typedef {import('@endo/patterns').Matcher & { validatedType?: T }} TypedMatcher + * @typedef {import('@endo/patterns').Pattern & { validatedType?: T }} TypedPattern */ /** - * @template {TypedMatcher} TM - * @typedef {TM extends TypedMatcher ? T : never} MatchedType + * @template {TypedPattern} TM + * @typedef {TM extends TypedPattern ? T : never} MatchedType */