Skip to content

Commit

Permalink
feat(orchestration): ZCFTools (#10057)
Browse files Browse the repository at this point in the history
closes: #9773

## Description

Provide selected ZCF APIs for  use in orchestration flows. For example: use vows for resumable promises.

### Security Considerations

nothing new

### Scaling Considerations

n/a

### Documentation Considerations

 - [x] reference docs for attenuated ZCF

### Testing Considerations

 - [x] unit tests
 - [x] a bit of integration with one of the examples

### Upgrade Considerations

n/a
  • Loading branch information
mergify[bot] authored Sep 24, 2024
2 parents ad7f655 + cb20148 commit f991204
Show file tree
Hide file tree
Showing 15 changed files with 175 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,6 @@ const contract = async (
makeCombineInvitationMakers,
makeExtraInvitationMaker,
flows,
zcf,
zoeTools,
});

Expand Down
1 change: 0 additions & 1 deletion packages/orchestration/src/examples/swap.contract.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ const contract = async (
const { brands } = zcf.getTerms();

const { stakeAndSwap } = orchestrateAll(flows, {
zcf,
localTransfer: zoeTools.localTransfer,
});

Expand Down
9 changes: 7 additions & 2 deletions packages/orchestration/src/examples/unbond.contract.js
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
8 changes: 4 additions & 4 deletions packages/orchestration/src/examples/unbond.flows.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
7 changes: 7 additions & 0 deletions packages/orchestration/src/facade.js
Original file line number Diff line number Diff line change
Expand Up @@ -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<GF>} */ (
(...args) => {
Expand Down
9 changes: 9 additions & 0 deletions packages/orchestration/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'];
}
5 changes: 5 additions & 0 deletions packages/orchestration/src/utils/start-helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
};
Expand Down
35 changes: 35 additions & 0 deletions packages/orchestration/src/utils/zcf-tools.js
Original file line number Diff line number Diff line change
@@ -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<ZcfTools>}
*/
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);
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
Binary file not shown.
2 changes: 0 additions & 2 deletions packages/orchestration/test/facade.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ test('calls between flows', async t => {

const { outer, outer2, inner } = orchestrateAll(flows, {
peerFlows: flows,
zcf,
});

t.deepEqual(await vt.when(inner('a', 'b', 'c')), 'a b c');
Expand All @@ -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');
Expand Down
20 changes: 20 additions & 0 deletions packages/orchestration/test/fixtures/zcfTester.contract.js
Original file line number Diff line number Diff line change
@@ -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);
1 change: 0 additions & 1 deletion packages/orchestration/test/fixtures/zoe-tools.contract.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@ const contract = async (
const creatorFacet = prepareChainHubAdmin(zone, chainHub);

const orchFns = orchestrateAll(flows, {
zcf,
contractState,
zoeTools,
});
Expand Down
76 changes: 76 additions & 0 deletions packages/orchestration/test/utils/zcf-tools.test.ts
Original file line number Diff line number Diff line change
@@ -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<ReturnType<typeof makeTestContext>>;

const test = anyTest as TestFn<TestContext>;

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/,
});
});

0 comments on commit f991204

Please sign in to comment.