Skip to content

Commit

Permalink
test: kitchen sink fixture for async-flow continuing offers
Browse files Browse the repository at this point in the history
  • Loading branch information
0xpatrickdev committed Jul 10, 2024
1 parent ca61a90 commit f1f07bb
Show file tree
Hide file tree
Showing 6 changed files with 364 additions and 1 deletion.
45 changes: 45 additions & 0 deletions packages/boot/test/bootstrapTests/orchestration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,3 +227,48 @@ test.serial('revise chain info', async t => {
client_id: '07-tendermint-3',
});
});

test('kitchen-sink', async t => {
const { buildProposal, evalProposal, agoricNamesRemotes, readLatest } =
t.context;

await evalProposal(
buildProposal(
'@agoric/builders/scripts/orchestration/init-kitchen-sink.js',
),
);

const wd =
await t.context.walletFactoryDriver.provideSmartWallet('agoric1test');

await wd.executeOffer({
id: 'request-coa',
invitationSpec: {
source: 'agoricContract',
instancePath: ['kitchenSink'],
callPipe: [['makeCosmosOrchAcctInvitation']],
},
offerArgs: {
chainName: 'cosmoshub',
},
proposal: {},
});
t.like(wd.getCurrentWalletRecord(), {
offerToPublicSubscriberPaths: [
[
'request-coa',
{
// FIXME in this PR
account: { payload: { vowV0: { vowV0: undefined } } },
// account: 'published.kitchenSink.cosmos1test',
},
],
],
});
t.like(wd.getLatestUpdateRecord(), {
status: { id: 'request-coa', numWantsSatisfied: 1 },
});
t.is(readLatest('published.kitchenSink'), '');
// FIXME in this PR
t.not(readLatest('published.kitchenSink.cosmos1test'), '');
});
25 changes: 25 additions & 0 deletions packages/builders/scripts/orchestration/init-kitchen-sink.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { makeHelpers } from '@agoric/deploy-script-support';

/** @type {import('@agoric/deploy-script-support/src/externalTypes.js').CoreEvalBuilder} */
export const defaultProposalBuilder = async ({ publishRef, install }) => {
return harden({
sourceSpec: '@agoric/orchestration/src/proposals/start-kitchen-sink.js',
getManifestCall: [
'getManifestForContract',
{
installKeys: {
kitchenSink: publishRef(
install(
'@agoric/orchestration/src/examples/kitchen-sink.contract.js',
),
),
},
},
],
});
};

export default async (homeP, endowments) => {
const { writeCoreEval } = await makeHelpers(homeP, endowments);
await writeCoreEval('start-kitchen-sink', defaultProposalBuilder);
};
103 changes: 103 additions & 0 deletions packages/orchestration/src/examples/kitchen-sink.contract.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/**
* @file Primarily a testing fixture, but also serves as an example of how to
* leverage basic functionality of the Orchestration API with async-flow.
*/
import { InvitationShape } from '@agoric/zoe/src/typeGuards.js';
import { M, mustMatch } from '@endo/patterns';
import { provideOrchestration } from '../utils/start-helper.js';

/**
* @import {Baggage} from '@agoric/vat-data';
* @import {Orchestrator} from '@agoric/orchestration';
* @import {OrchestrationPowers} from '../utils/start-helper.js';
*/

/**
* Create an account on the local chain and return a continuing offer with
* invitations for Delegate, WithdrawRewards, Transfer, etc.
*
* @param {Orchestrator} orch
* @param {undefined} _ctx
* @param {ZCFSeat} seat
*/
const makeLocalOrchAcctHandler = async (orch, _ctx, seat) => {
seat.exit(); // no funds exchanged
const agoric = await orch.getChain('agoric');
const localAccount = await agoric.makeAccount();
// @ts-expect-error asContinuingOffer does not exist on OrchestrationAccountI
return localAccount.asContinuingOffer();
};

/**
* Create an account on a Cosmos chain and return a continuing offer with
* invitations makers for Delegate, WithdrawRewards, Transfer, etc.
*
* @param {Orchestrator} orch
* @param {undefined} _ctx
* @param {ZCFSeat} seat
* @param {{ chainName: string }} offerArgs
*/
const makeCosmosOrchAcctHandler = async (orch, _ctx, seat, { chainName }) => {
seat.exit(); // no funds exchanged
mustMatch(chainName, M.string());
const remoteChain = await orch.getChain(chainName);
const cosmosAccount = await remoteChain.makeAccount();
// @ts-expect-error asContinuingOffer does not exist on OrchestrationAccountI
return cosmosAccount.asContinuingOffer();
};

/**
* @param {ZCF} zcf
* @param {OrchestrationPowers & {
* marshaller: Marshaller;
* }} privateArgs
* @param {Baggage} baggage
*/
export const start = async (zcf, privateArgs, baggage) => {
const { orchestrate, zone } = provideOrchestration(
zcf,
baggage,
privateArgs,
privateArgs.marshaller,
);

/** @type {OfferHandler} */
const makeLocalOrchAccount = orchestrate(
'makeLocalAccount',
undefined,
makeLocalOrchAcctHandler,
);

/** @type {OfferHandler} */
const makeCosmosOrchAccount = orchestrate(
'makeCosmosICAAccount',
undefined,
makeCosmosOrchAcctHandler,
);

const publicFacet = zone.exo(
'Kitchen Sink Public Facet',
M.interface('Kitchen Sink PF', {
makeLocalOrchAcctInvitation: M.callWhen().returns(InvitationShape),
makeCosmosOrchAcctInvitation: M.callWhen().returns(InvitationShape),
}),
{
makeLocalOrchAcctInvitation() {
return zcf.makeInvitation(
makeLocalOrchAccount,
'Make Local Orchestration Account',
);
},
makeCosmosOrchAcctInvitation() {
return zcf.makeInvitation(
makeCosmosOrchAccount,
'Make Cosmos Orchestration Account',
);
},
},
);

return { publicFacet };
};

/** @typedef {typeof start} KitchenSinkSF */
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,6 @@ export const prepareLocalOrchestrationAccountKit = (
// TODO https://github.com/Agoric/agoric-sdk/issues/9610
return asVow(() => Fail`not yet implemented`);
},

getPublicTopics() {
const { topicKit } = this.state;
return harden({
Expand Down
97 changes: 97 additions & 0 deletions packages/orchestration/src/proposals/start-kitchen-sink.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/**
* @file A proposal to start the kitchen sink contract.
*/
import { makeTracer } from '@agoric/internal';
import { makeStorageNodeChild } from '@agoric/internal/src/lib-chainStorage.js';
import { E } from '@endo/far';

/**
* @import {KitchenSinkSF} from '../examples/kitchen-sink.contract.js';
*/

const trace = makeTracer('StartKitchenSink', true);
const contractName = 'kitchenSink';

/**
* See `@agoric/builders/builders/scripts/orchestration/init-kitchen-sink.js`
* for the accompanying proposal builder. Run `agoric run
* packages/builders/scripts/orchestration/init-kitchen-sink.js` to build the
* contract and proposal files.
*
* @param {BootstrapPowers} powers
*/
export const startKitchenSink = async ({
consume: {
agoricNames,
board,
chainStorage,
chainTimerService,
cosmosInterchainService,
localchain,
startUpgradable,
},
installation: {
// @ts-expect-error not a WellKnownName
consume: { [contractName]: installation },
},
instance: {
// @ts-expect-error not a WellKnownName
produce: { [contractName]: produceInstance },
},
}) => {
trace(`start ${contractName}`);
await null;

const storageNode = await makeStorageNodeChild(chainStorage, contractName);
const marshaller = await E(board).getPublishingMarshaller();

/** @type {StartUpgradableOpts<KitchenSinkSF>} */
const startOpts = {
label: 'kitchenSink',
installation,
terms: undefined,
privateArgs: {
agoricNames: await agoricNames,
orchestrationService: await cosmosInterchainService,
localchain: await localchain,
storageNode,
marshaller,
timerService: await chainTimerService,
},
};

const { instance } = await E(startUpgradable)(startOpts);
produceInstance.resolve(instance);
};
harden(startKitchenSink);

export const getManifestForContract = (
{ restoreRef },
{ installKeys, ...options },
) => {
return {
manifest: {
[startKitchenSink.name]: {
consume: {
agoricNames: true,
board: true,
chainStorage: true,
chainTimerService: true,
cosmosInterchainService: true,
localchain: true,
startUpgradable: true,
},
installation: {
consume: { [contractName]: true },
},
instance: {
produce: { [contractName]: true },
},
},
},
installations: {
[contractName]: restoreRef(installKeys[contractName]),
},
options,
};
};
94 changes: 94 additions & 0 deletions packages/orchestration/test/examples/kitchen-sink.contract.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { test } from '@agoric/zoe/tools/prepare-test-env-ava.js';
import { setUpZoeForTest } from '@agoric/zoe/tools/setup-zoe.js';
import { E, getInterfaceOf } from '@endo/far';
import path from 'path';
import { commonSetup } from '../supports.js';

const dirname = path.dirname(new URL(import.meta.url).pathname);

const contractName = 'kitchen-sink';
const contractFile = `${dirname}/../../src/examples/${contractName}.contract.js`;
type StartFn =
typeof import('../../src/examples/kitchen-sink.contract.js').start;

test('orchestrate - LocalOrchAccount returns a ContinuingOfferResult', async t => {
t.log('bootstrap, orchestration core-eval');
const {
bootstrap: { storage },
commonPrivateArgs,
} = await commonSetup(t);

const { zoe, bundleAndInstall } = await setUpZoeForTest();

t.log('contract coreEval', contractName);

const installation: Installation<StartFn> =
await bundleAndInstall(contractFile);

const storageNode = await E(storage.rootNode).makeChildNode(contractName);
const kitchenSinkKit = await E(zoe).startInstance(
installation,
undefined,
{},
{ ...commonPrivateArgs, storageNode },
);

const publicFacet = await E(zoe).getPublicFacet(kitchenSinkKit.instance);
const inv = E(publicFacet).makeLocalOrchAcctInvitation();
const userSeat = E(zoe).offer(inv, {});
// @ts-expect-error TODO: type expected offer result
const { holder, invitationMakers, publicSubscribers } =
await E(userSeat).getOfferResult();

t.regex(getInterfaceOf(holder)!, /Local Orchestration (.*) holder/);
t.regex(
getInterfaceOf(invitationMakers)!,
/Local Orchestration (.*) invitationMakers/,
);
const { description, storagePath, subscriber } = publicSubscribers.account;
t.regex(description, /Account holder/);
// FIXME in this PR, currently a vow
t.is(getInterfaceOf(storagePath), undefined);
t.regex(getInterfaceOf(subscriber)!, /Durable Publish Kit subscriber/);
});

test('orchestrate - CosmosOrchAccount returns a ContinuingOfferResult', async t => {
t.log('bootstrap, orchestration core-eval');
const {
bootstrap: { storage },
commonPrivateArgs,
} = await commonSetup(t);

const { zoe, bundleAndInstall } = await setUpZoeForTest();

t.log('contract coreEval', contractName);

const installation: Installation<StartFn> =
await bundleAndInstall(contractFile);

const storageNode = await E(storage.rootNode).makeChildNode(contractName);
const kitchenSinkKit = await E(zoe).startInstance(
installation,
undefined,
{},
{ ...commonPrivateArgs, storageNode },
);

const publicFacet = await E(zoe).getPublicFacet(kitchenSinkKit.instance);
const inv = E(publicFacet).makeCosmosOrchAcctInvitation();
const userSeat = E(zoe).offer(inv, {}, undefined, { chainName: 'cosmoshub' });
// @ts-expect-error TODO: type expected offer result
const { holder, invitationMakers, publicSubscribers } =
await E(userSeat).getOfferResult();

t.regex(getInterfaceOf(holder)!, /Staking Account (.*) holder/);
t.regex(
getInterfaceOf(invitationMakers)!,
/Staking Account (.*) invitationMakers/,
);
const { description, storagePath, subscriber } = publicSubscribers.account;
t.regex(description, /Account holder/);
// FIXME in this PR, currently a vow
t.is(getInterfaceOf(storagePath), undefined);
t.regex(getInterfaceOf(subscriber)!, /Durable Publish Kit subscriber/);
});

0 comments on commit f1f07bb

Please sign in to comment.