Skip to content

Commit

Permalink
test: basic-flows.contract.js for testing 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 bc12cbd commit 8330fba
Show file tree
Hide file tree
Showing 11 changed files with 350 additions and 3 deletions.
65 changes: 65 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,68 @@ test.serial('revise chain info', async t => {
client_id: '07-tendermint-3',
});
});

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

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

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

// create a cosmos orchestration account
await wd.executeOffer({
id: 'request-coa',
invitationSpec: {
source: 'agoricContract',
instancePath: ['basicFlows'],
callPipe: [['makeOrchAccountInvitation']],
},
offerArgs: {
chainName: 'cosmoshub',
},
proposal: {},
});
t.like(wd.getCurrentWalletRecord(), {
offerToPublicSubscriberPaths: [
[
'request-coa',
{
account: 'published.basicFlows.cosmos1test',
},
],
],
});
t.like(wd.getLatestUpdateRecord(), {
status: { id: 'request-coa', numWantsSatisfied: 1 },
});
t.is(readLatest('published.basicFlows.cosmos1test'), '');

// create a local orchestration account
await wd.executeOffer({
id: 'request-loa',
invitationSpec: {
source: 'agoricContract',
instancePath: ['basicFlows'],
callPipe: [['makeOrchAccountInvitation']],
},
offerArgs: {
chainName: 'agoric',
},
proposal: {},
});

const publicSubscriberPaths = Object.fromEntries(
wd.getCurrentWalletRecord().offerToPublicSubscriberPaths,
);
t.deepEqual(publicSubscriberPaths['request-loa'], {
account: 'published.basicFlows.agoric1mockVlocalchainAddress',
});
t.like(wd.getLatestUpdateRecord(), {
status: { id: 'request-loa', numWantsSatisfied: 1 },
});
t.is(readLatest('published.basicFlows.agoric1mockVlocalchainAddress'), '');
});
1 change: 1 addition & 0 deletions packages/builders/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"@agoric/deploy-script-support": "^0.10.3",
"@agoric/governance": "^0.10.3",
"@agoric/inter-protocol": "^0.16.1",
"@agoric/orchestration": "^0.1.0",
"@agoric/store": "^0.9.2",
"@agoric/swing-store": "^0.9.1",
"@agoric/swingset-liveslots": "^0.10.2",
Expand Down
26 changes: 26 additions & 0 deletions packages/builders/scripts/orchestration/init-basic-flows.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { makeHelpers } from '@agoric/deploy-script-support';
import { startBasicFlows } from '@agoric/orchestration/src/proposals/start-basic-flows.js';

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

export default async (homeP, endowments) => {
const { writeCoreEval } = await makeHelpers(homeP, endowments);
await writeCoreEval(startBasicFlows.name, defaultProposalBuilder);
};
72 changes: 72 additions & 0 deletions packages/orchestration/src/examples/basic-flows.contract.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/**
* @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 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 makeOrchAccountHandler = 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();
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 makeOrchAccount = orchestrate(
'makeOrchAccount',
undefined,
makeOrchAccountHandler,
);

const publicFacet = zone.exo(
'Basic Flows Public Facet',
M.interface('Basic Flows PF', {
makeOrchAccountInvitation: M.callWhen().returns(InvitationShape),
}),
{
makeOrchAccountInvitation() {
return zcf.makeInvitation(
makeOrchAccount,
'Make an Orchestration Account',
);
},
},
);

return { publicFacet };
};

/** @typedef {typeof start} BasicFlowsSF */
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ export const prepareCosmosOrchestrationAccountKit = (
zcf,
) => {
const makeCosmosOrchestrationAccountKit = zone.exoClassKit(
'Staking Account Holder',
'Cosmos Orchestration Account Holder',
{
helper: M.interface('helper', {
owned: M.call().returns(M.remotable()),
Expand Down
97 changes: 97 additions & 0 deletions packages/orchestration/src/proposals/start-basic-flows.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/**
* @file A proposal to start the basic flows contract.
*/
import { makeTracer } from '@agoric/internal';
import { makeStorageNodeChild } from '@agoric/internal/src/lib-chainStorage.js';
import { E } from '@endo/far';

/**
* @import {BasicFlowsSF} from '../examples/basic-flows.contract.js';
*/

const trace = makeTracer('StartBasicFlows', true);
const contractName = 'basicFlows';

/**
* See `@agoric/builders/builders/scripts/orchestration/init-basic-flows.js` for
* the accompanying proposal builder. Run `agoric run
* packages/builders/scripts/orchestration/init-basic-flows.js` to build the
* contract and proposal files.
*
* @param {BootstrapPowers} powers
*/
export const startBasicFlows = 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<BasicFlowsSF>} */
const startOpts = {
label: 'basicFlows',
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(startBasicFlows);

export const getManifestForContract = (
{ restoreRef },
{ installKeys, ...options },
) => {
return {
manifest: {
[startBasicFlows.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,
};
};
86 changes: 86 additions & 0 deletions packages/orchestration/test/examples/basic-flows.contract.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { test as anyTest } from '@agoric/zoe/tools/prepare-test-env-ava.js';
import type { TestFn } from 'ava';
import { setUpZoeForTest } from '@agoric/zoe/tools/setup-zoe.js';
import type { Instance } from '@agoric/zoe/src/zoeService/utils.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 = 'basic-flows';
const contractFile = `${dirname}/../../src/examples/${contractName}.contract.js`;
type StartFn =
typeof import('../../src/examples/basic-flows.contract.js').start;

type TestContext = Awaited<ReturnType<typeof commonSetup>> & {
zoe: ZoeService;
instance: Instance<StartFn>;
};

const test = anyTest as TestFn<TestContext>;

test.before(async t => {
const setupContext = await commonSetup(t);
const {
bootstrap: { storage },
commonPrivateArgs,
} = setupContext;

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

t.log('contract coreEval', contractName);
const installation = await bundleAndInstall(contractFile);

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

t.context = {
...setupContext,
zoe,
instance,
};
});

const chainConfigs = {
agoric: { addressPrefix: 'agoric1fakeLCAAddress' },
cosmoshub: { addressPrefix: 'cosmos1test' },
};

const orchestrationAccountScenario = test.macro({
title: (_, chainName: string) =>
`orchestrate - ${chainName} makeOrchAccount returns a ContinuingOfferResult`,
exec: async (t, chainName: string) => {
const config = chainConfigs[chainName as keyof typeof chainConfigs];
if (!config) {
return t.fail(`Unknown chain: ${chainName}`);
}

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

t.regex(getInterfaceOf(holder)!, /Orchestration (.*) holder/);
t.regex(getInterfaceOf(invitationMakers)!, /invitationMakers/);

const { description, storagePath, subscriber } = publicSubscribers.account;
t.regex(description, /Account holder/);

const expectedStoragePath = `mockChainStorageRoot.basic-flows.${config.addressPrefix}`;
t.is(storagePath, expectedStoragePath);

t.regex(getInterfaceOf(subscriber)!, /Durable Publish Kit subscriber/);
},
});

test(orchestrationAccountScenario, 'agoric');
test(orchestrationAccountScenario, 'cosmoshub');
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@ Generated by [AVA](https://avajs.dev).
'Send PF_singleton': 'Alleged: Send PF',
},
orchestration: {
'Cosmos Orchestration Account Holder_kindHandle': 'Alleged: kind',
'Local Orchestration Account Kit_kindHandle': 'Alleged: kind',
LocalChainFacade_kindHandle: 'Alleged: kind',
Orchestrator_kindHandle: 'Alleged: kind',
RemoteChainFacade_kindHandle: 'Alleged: kind',
'Staking Account Holder_kindHandle': 'Alleged: kind',
sendIt: {
asyncFlow_kindHandle: 'Alleged: kind',
endowments: {
Expand Down
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,14 @@ Generated by [AVA](https://avajs.dev).
publicFacet_singleton: 'Alleged: publicFacet',
},
orchestration: {
'Cosmos Orchestration Account Holder_kindHandle': 'Alleged: kind',
LSTTia: {
asyncFlow_kindHandle: 'Alleged: kind',
},
'Local Orchestration Account Kit_kindHandle': 'Alleged: kind',
LocalChainFacade_kindHandle: 'Alleged: kind',
Orchestrator_kindHandle: 'Alleged: kind',
RemoteChainFacade_kindHandle: 'Alleged: kind',
'Staking Account Holder_kindHandle': 'Alleged: kind',
},
vows: {
PromiseWatcher_kindHandle: 'Alleged: kind',
Expand Down
Binary file not shown.

0 comments on commit 8330fba

Please sign in to comment.