From cb201485e82f4d97609910eebe72074c6b0a7092 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Tue, 10 Sep 2024 13:53:25 -0500 Subject: [PATCH] feat(orchestration): ZcfTools for use in flows - unit tests - zcfTester copied from @agoric/zoe - don't pass zcf thru context in examples - restrict makeInvitation handler to passable - regenerate baggage snapshots --- .../src/examples/send-anywhere.contract.js | 1 - .../examples/staking-combinations.contract.js | 1 - .../src/examples/swap.contract.js | 1 - .../src/examples/unbond.contract.js | 9 ++- .../src/examples/unbond.flows.js | 8 +- packages/orchestration/src/facade.js | 7 ++ packages/orchestration/src/types.ts | 9 +++ .../orchestration/src/utils/start-helper.js | 5 ++ packages/orchestration/src/utils/zcf-tools.js | 35 ++++++++ .../snapshots/unbond.contract.test.ts.md | 12 +++ .../snapshots/unbond.contract.test.ts.snap | Bin 981 -> 1139 bytes packages/orchestration/test/facade.test.ts | 2 - .../test/fixtures/zcfTester.contract.js | 20 +++++ .../test/fixtures/zoe-tools.contract.js | 1 - .../test/utils/zcf-tools.test.ts | 76 ++++++++++++++++++ 15 files changed, 175 insertions(+), 12 deletions(-) create mode 100644 packages/orchestration/src/utils/zcf-tools.js create mode 100644 packages/orchestration/test/fixtures/zcfTester.contract.js create mode 100644 packages/orchestration/test/utils/zcf-tools.test.ts diff --git a/packages/orchestration/src/examples/send-anywhere.contract.js b/packages/orchestration/src/examples/send-anywhere.contract.js index 54aace33d0e..4fc4690fa2e 100644 --- a/packages/orchestration/src/examples/send-anywhere.contract.js +++ b/packages/orchestration/src/examples/send-anywhere.contract.js @@ -45,7 +45,6 @@ const contract = async ( // orchestrate uses the names on orchestrationFns to do a "prepare" of the associated behavior const orchFns = orchestrateAll(flows, { - zcf, contractState, zoeTools, }); diff --git a/packages/orchestration/src/examples/staking-combinations.contract.js b/packages/orchestration/src/examples/staking-combinations.contract.js index 209b328f674..2158a4560bb 100644 --- a/packages/orchestration/src/examples/staking-combinations.contract.js +++ b/packages/orchestration/src/examples/staking-combinations.contract.js @@ -133,7 +133,6 @@ const contract = async ( makeCombineInvitationMakers, makeExtraInvitationMaker, flows, - zcf, zoeTools, }); diff --git a/packages/orchestration/src/examples/swap.contract.js b/packages/orchestration/src/examples/swap.contract.js index fa2a7e0bff1..e726d371cf1 100644 --- a/packages/orchestration/src/examples/swap.contract.js +++ b/packages/orchestration/src/examples/swap.contract.js @@ -62,7 +62,6 @@ const contract = async ( const { brands } = zcf.getTerms(); const { stakeAndSwap } = orchestrateAll(flows, { - zcf, localTransfer: zoeTools.localTransfer, }); diff --git a/packages/orchestration/src/examples/unbond.contract.js b/packages/orchestration/src/examples/unbond.contract.js index e577f2c6fec..dda006c31bf 100644 --- a/packages/orchestration/src/examples/unbond.contract.js +++ b/packages/orchestration/src/examples/unbond.contract.js @@ -27,8 +27,13 @@ import * as flows from './unbond.flows.js'; * @param {Zone} zone * @param {OrchestrationTools} tools */ -const contract = async (zcf, privateArgs, zone, { orchestrateAll }) => { - const { unbondAndTransfer } = orchestrateAll(flows, { zcf }); +const contract = async ( + zcf, + privateArgs, + zone, + { orchestrateAll, zcfTools }, +) => { + const { unbondAndTransfer } = orchestrateAll(flows, { zcfTools }); const publicFacet = zone.exo('publicFacet', undefined, { makeUnbondAndTransferInvitation() { diff --git a/packages/orchestration/src/examples/unbond.flows.js b/packages/orchestration/src/examples/unbond.flows.js index 99f19e3655f..d5672666fb3 100644 --- a/packages/orchestration/src/examples/unbond.flows.js +++ b/packages/orchestration/src/examples/unbond.flows.js @@ -3,17 +3,17 @@ import { makeTracer } from '@agoric/internal'; const trace = makeTracer('UnbondAndTransfer'); /** - * @import {Orchestrator, OrchestrationFlow, CosmosDelegationResponse} from '../types.js' + * @import {Orchestrator, OrchestrationFlow, CosmosDelegationResponse, ZcfTools} from '../types.js' */ /** * @satisfies {OrchestrationFlow} * @param {Orchestrator} orch * @param {object} ctx - * @param {ZCF} ctx.zcf + * @param {ZcfTools} ctx.zcfTools */ -export const unbondAndTransfer = async (orch, { zcf }) => { - trace('zcf within the membrane', zcf); +export const unbondAndTransfer = async (orch, { zcfTools }) => { + trace('zcfTools within the membrane', zcfTools); // Osmosis is one of the few chains with icqEnabled const osmosis = await orch.getChain('osmosis'); const osmoDenom = (await osmosis.getChainInfo()).stakingTokens[0].denom; diff --git a/packages/orchestration/src/facade.js b/packages/orchestration/src/facade.js index 64a2e48e73f..d2fe5b595d8 100644 --- a/packages/orchestration/src/facade.js +++ b/packages/orchestration/src/facade.js @@ -78,6 +78,13 @@ export const makeOrchestrationFacade = ({ const [wrappedCtx] = prepareEndowment(subZone, 'endowments', [hostCtx]); const hostFn = asyncFlow(subZone, 'asyncFlow', guestFn); + deepMapObject( + wrappedCtx, + val => + val === zcf && + assert.fail('do not use zcf in orchestration context; try zcfTools'), + ); + // cast because return could be arbitrary subtype const orcFn = /** @type {HostForGuest} */ ( (...args) => { diff --git a/packages/orchestration/src/types.ts b/packages/orchestration/src/types.ts index a8a0bf4d2b4..f87f99d3461 100644 --- a/packages/orchestration/src/types.ts +++ b/packages/orchestration/src/types.ts @@ -11,3 +11,12 @@ export type * from './orchestration-api.js'; export type * from './exos/cosmos-interchain-service.js'; export type * from './exos/chain-hub.js'; export type * from './vat-orchestration.js'; + +/** + * ({@link ZCF})-like tools for use in {@link OrchestrationFlow}s. + */ +export interface ZcfTools { + assertUniqueKeyword: ZCF['assertUniqueKeyword']; + atomicRearrange: ZCF['atomicRearrange']; + makeInvitation: ZCF['makeInvitation']; +} diff --git a/packages/orchestration/src/utils/start-helper.js b/packages/orchestration/src/utils/start-helper.js index e3b2cd7aa9d..7aedda2f1b8 100644 --- a/packages/orchestration/src/utils/start-helper.js +++ b/packages/orchestration/src/utils/start-helper.js @@ -10,6 +10,7 @@ import { prepareOrchestrator } from '../exos/orchestrator.js'; import { prepareRemoteChainFacade } from '../exos/remote-chain-facade.js'; import { makeOrchestrationFacade } from '../facade.js'; import { makeZoeTools } from './zoe-tools.js'; +import { makeZcfTools } from './zcf-tools.js'; /** * @import {LocalChain} from '@agoric/vats/src/localchain.js'; @@ -76,6 +77,8 @@ export const provideOrchestration = ( const zoeTools = makeZoeTools(zcf, vowTools); + const zcfTools = makeZcfTools(zcf, vowTools); + const { makeRecorderKit } = prepareRecorderKitMakers(baggage, marshaller); const makeLocalOrchestrationAccountKit = prepareLocalOrchestrationAccountKit( zones.orchestration, @@ -164,12 +167,14 @@ export const provideOrchestration = ( const defaultOrchestrateKit = makeOrchestrateKit( zones.contract.subZone('orchestration'), ); + return { ...defaultOrchestrateKit, makeOrchestrateKit, chainHub, vowTools, asyncFlowTools, + zcfTools, zoeTools, zone: zones.contract, }; diff --git a/packages/orchestration/src/utils/zcf-tools.js b/packages/orchestration/src/utils/zcf-tools.js new file mode 100644 index 00000000000..d3843695f2e --- /dev/null +++ b/packages/orchestration/src/utils/zcf-tools.js @@ -0,0 +1,35 @@ +/** + * @import {HostInterface} from '@agoric/async-flow'; + * @import {VowTools} from '@agoric/vow'; + * @import {ZcfTools} from '../types.js'; + */ + +import { M, mustMatch } from '@endo/patterns'; + +const HandlerShape = M.remotable('OfferHandler'); + +/** + * @param {ZCF} zcf + * @param {VowTools} vowTools + * @returns {HostInterface} + */ +export const makeZcfTools = (zcf, vowTools) => + harden({ + makeInvitation(offerHandler, description, customDetails, proposalShape) { + mustMatch(offerHandler, HandlerShape); + return vowTools.watch( + zcf.makeInvitation( + offerHandler, + description, + customDetails, + proposalShape, + ), + ); + }, + atomicRearrange(transfers) { + zcf.atomicRearrange(transfers); + }, + assertUniqueKeyword(keyword) { + zcf.assertUniqueKeyword(keyword); + }, + }); diff --git a/packages/orchestration/test/examples/snapshots/unbond.contract.test.ts.md b/packages/orchestration/test/examples/snapshots/unbond.contract.test.ts.md index 1a7163c5994..aeb1e506366 100644 --- a/packages/orchestration/test/examples/snapshots/unbond.contract.test.ts.md +++ b/packages/orchestration/test/examples/snapshots/unbond.contract.test.ts.md @@ -28,6 +28,18 @@ Generated by [AVA](https://avajs.dev). orchestration: { unbondAndTransfer: { asyncFlow_kindHandle: 'Alleged: kind', + endowments: { + 0: { + zcfTools: { + assertUniqueKeyword_kindHandle: 'Alleged: kind', + assertUniqueKeyword_singleton: 'Alleged: assertUniqueKeyword', + atomicRearrange_kindHandle: 'Alleged: kind', + atomicRearrange_singleton: 'Alleged: atomicRearrange', + makeInvitation_kindHandle: 'Alleged: kind', + makeInvitation_singleton: 'Alleged: makeInvitation', + }, + }, + }, }, }, publicFacet_kindHandle: 'Alleged: kind', diff --git a/packages/orchestration/test/examples/snapshots/unbond.contract.test.ts.snap b/packages/orchestration/test/examples/snapshots/unbond.contract.test.ts.snap index fbf713b176456c4563af10a276dffac5d86a43b3..64ebc2429efc493abb01d86e2823bc89f2d91893 100644 GIT binary patch literal 1139 zcmV-(1dRJZRzVF00000000A>S50plMHqg@AM5p3J`{&0Z6M}=gs8-a1OoMdU8@G*cN0Q?5v9{`I4m?O?CaW-1-aApV@f1R0`0jYvGAk&y5nHtH0z91D{@*Dw1;Ud+$ zf;9srO2xLhHi{ZwiYWXj7S8R+fgbKtWjO;pl>xjA@O1`w zkO7{~0y|mYyDac$7O3Tbmvg|aoV9nE$-Q$)?$aFbSq`|D1OBq%jtwuqmOrp4Dt1ycVujh-#Xp7Ug~T$>js+56+v%5O z=bDjw3hhT*5IHUz>AUXn%{ig{SbsQWSd%iI zddkelxrrLq#?%-rO3}4+o?Vr?Cv|-zo@c$mm<)xfugTzKgKR?cnah&V;26nMOw@Cc z-s(2>MUyh>rS{vI!&GGI)huoDo-{n&7* zQtngM<>nx`Z?DNVx~gQ)*Zd|mY&(6K&fbuNbz!&?G|;Cg%Y5|QE#n9J1XOo_T<04~ z(Or@qeVd-19>+$bJZDY*$!Oj_7czHU;GS!p2L&6J{n-V6b%A=(f>skJ&D%wwT?D== z0uOEYR3d>_O2Dfn3pSlNkVsVG_e-(y6N;7;UPE}-6&1QY9I;i9jJ4B+s7HN0`u*g1 zZxh};Xn0G&(MI_mPs2aLZ_Dc|tD`)xap7@wnr8M}Y?JFSFsHR`LLf2z7RP)WJF0`# zM~97evK6@``(+9Ewgmic*WFFj-7aU1rnrMl5t1t(I;O;hCkH()OmwNsoG$|}l&uts z$5`)TOo%)m@w;WCcrq`&gm{6sbjhuKU* zZKWRV@^!J}8+w{cRy~g!MHqf&yH*Qub_tMLwr)( z1+Wj`V*oz@_zl2+0JaFQLA-V19dzI0%n&kvU0YiNX@YnltJok;i&Q|W>*Sf&5=FZ) z53VNNIMRdQjmW$+j6}FkMHusp)@;Wy@AL3tFvTp5jYH0)3b|Us@H8PW5Fmr=RNoP- z6U#{oeVBuMCWTRynH2dphcu;P1FnstCXynBALd~9ww&l`oKEdHz;g}|I>0v$@P`AO zuK>3zz;_kk?+VazftOw2mTSe%S&W@e+)rKLGZ%Q^0{_@_&!*R3tO6ISz$aDUmnyL1 z0dIT2=N|CA2mIt&67|B04?W;t4|u5tBsGiPEF^cuO3YR}97Up?g}7{9pSH|(B>FKo zQe=cPbL&N>rPJDtrd~!;l=Wb{lZe#tx|k?B9xqj53kv6o3WaT)oeLDmcBNmI{Uak4 zFYCtEk)ei{*~Mu>?$?fsx;1N*M6lOspR1d6$dyi6Kjv8Lb5@5&aiaK)@@JmXi{+#v z)j?vI9Pt~4Y}J$0%PVx8{n?zmHsN%5g^tq@q!m|5^?m=z&AFic)Ob2&ScfuRddh6( zZ3{VU%}sN()J5OYd45mok<`IK-p`=Tm`sET_GP@>AY0J9a9J`MKSA;Y6ZKrGw>O}X z=uk$(()D)c*cDlNH`|ANBn>aO{B*GA%An4s)^E}gPoY~m$d9`0nw0UZHb2TCD`U!r z+<j<+a#GSCt$^n%|^`4a%qK> { const { outer, outer2, inner } = orchestrateAll(flows, { peerFlows: flows, - zcf, }); t.deepEqual(await vt.when(inner('a', 'b', 'c')), 'a b c'); @@ -70,7 +69,6 @@ test('context mapping individual flows', async t => { const { outer } = orchestrateAll(flows, { peerFlows: { inner: flows.inner }, - zcf, }); t.deepEqual(await vt.when(outer('a', 'b', 'c')), 'Hello a b c'); diff --git a/packages/orchestration/test/fixtures/zcfTester.contract.js b/packages/orchestration/test/fixtures/zcfTester.contract.js new file mode 100644 index 00000000000..f01d03372da --- /dev/null +++ b/packages/orchestration/test/fixtures/zcfTester.contract.js @@ -0,0 +1,20 @@ +import { Far } from '@endo/far'; + +/** + * Tests ZCF + * + * @param {ZCF} zcf + */ +export const start = async zcf => { + // make the `zcf` and `instance` available to the tests + const instance = zcf.getInstance(); + zcf.setTestJig(() => harden({ instance })); + + const publicFacet = Far('public facet', { + makeInvitation: () => zcf.makeInvitation(() => 17, 'simple'), + }); + + return { publicFacet }; +}; + +harden(start); diff --git a/packages/orchestration/test/fixtures/zoe-tools.contract.js b/packages/orchestration/test/fixtures/zoe-tools.contract.js index 67e3ae5268c..4e501563f40 100644 --- a/packages/orchestration/test/fixtures/zoe-tools.contract.js +++ b/packages/orchestration/test/fixtures/zoe-tools.contract.js @@ -58,7 +58,6 @@ const contract = async ( const creatorFacet = prepareChainHubAdmin(zone, chainHub); const orchFns = orchestrateAll(flows, { - zcf, contractState, zoeTools, }); diff --git a/packages/orchestration/test/utils/zcf-tools.test.ts b/packages/orchestration/test/utils/zcf-tools.test.ts new file mode 100644 index 00000000000..510921a9497 --- /dev/null +++ b/packages/orchestration/test/utils/zcf-tools.test.ts @@ -0,0 +1,76 @@ +import { test as anyTest } from '@agoric/zoe/tools/prepare-test-env-ava.js'; + +import { AmountMath, makeIssuerKit } from '@agoric/ertp'; +import { prepareSwingsetVowTools } from '@agoric/vow'; +import { makeFakeVatAdmin } from '@agoric/zoe/tools/fakeVatAdmin.js'; +import { makeZoeKitForTest } from '@agoric/zoe/tools/setup-zoe.js'; +import { makeNodeBundleCache } from '@endo/bundle-source/cache.js'; +import { E, Far } from '@endo/far'; +import type { TestFn } from 'ava'; +import { createRequire } from 'node:module'; +import { makeZcfTools } from '../../src/utils/zcf-tools.js'; +import { provideDurableZone } from '../supports.js'; + +const nodeRequire = createRequire(import.meta.url); +const contractEntry = nodeRequire.resolve('../fixtures/zcfTester.contract.js'); + +const makeTestContext = async () => { + let testJig; + const setJig = jig => (testJig = jig); + const fakeVatAdmin = makeFakeVatAdmin(setJig); + const { zoeService: zoe, feeMintAccess } = makeZoeKitForTest( + fakeVatAdmin.admin, + ); + + const bundleCache = await makeNodeBundleCache('bundles', {}, s => import(s)); + const contractBundle = await bundleCache.load(contractEntry); + + fakeVatAdmin.vatAdminState.installBundle('b1-contract', contractBundle); + const installation = await E(zoe).installBundleID('b1-contract'); + + const stuff = makeIssuerKit('Stuff'); + await E(zoe).startInstance(installation, { Stuff: stuff.issuer }); + assert(testJig, 'startInstance did not call back to setTestJig'); + + const zcf: ZCF = testJig.zcf; + + const zone = provideDurableZone('root'); + const vt = prepareSwingsetVowTools(zone); + const zcfTools = makeZcfTools(zcf, vt); + return { zoe, zcf, stuff, feeMintAccess, zcfTools, vt }; +}; + +type TestContext = Awaited>; + +const test = anyTest as TestFn; + +test.before('set up context', async t => (t.context = await makeTestContext())); + +test('unchanged: atomicRearrange(), assertUniqueKeyword()', async t => { + const { zcf, zcfTools } = t.context; + + t.notThrows(() => zcfTools.atomicRearrange([])); + + t.notThrows(() => zcfTools.assertUniqueKeyword('K1')); + t.throws(() => zcfTools.assertUniqueKeyword('Stuff')); +}); + +test('changed: makeInvitation: watch promise', async t => { + const { zoe, zcf, zcfTools, vt } = t.context; + + const handler = Far('Trade', { handle: seat => {} }); + const toTradeVow = zcfTools.makeInvitation(handler, 'trade'); + + const toTrade = await vt.when(toTradeVow); + const amt = await E(E(zoe).getInvitationIssuer()).getAmountOf(toTrade); + t.like(amt, { value: [{ description: 'trade' }] }); +}); + +test('removed: makeInvitation: non-passable handler', async t => { + const { zcfTools } = t.context; + + const handler = harden(_seat => {}); + t.throws(() => zcfTools.makeInvitation(handler, 'trade'), { + message: /Remotables must be explicitly declared/, + }); +});