Skip to content

Commit

Permalink
feat(orchestration): ZcfTools for use in flows
Browse files Browse the repository at this point in the history
 - unit tests
   - zcfTester copied from @agoric/zoe
 - don't pass zcf thru context in examples
 - restrict makeInvitation handler to passable
 - regenerate baggage snapshots
  • Loading branch information
dckc committed Sep 20, 2024
1 parent 741bbd1 commit d1e234f
Show file tree
Hide file tree
Showing 13 changed files with 175 additions and 9 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.
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);
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 d1e234f

Please sign in to comment.