Skip to content

Commit

Permalink
test: continuing-invitation
Browse files Browse the repository at this point in the history
  • Loading branch information
turadg committed Sep 5, 2024
1 parent dbf4e15 commit 5b70d25
Show file tree
Hide file tree
Showing 5 changed files with 379 additions and 0 deletions.
118 changes: 118 additions & 0 deletions packages/orchestration/src/examples/continuing-invitation.contract.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/**
* @file This contract demonstrates the continuing invitation pattern with async
* flows.
*
* The primary offer result is a power for invitation makers that can perform
* actions with an ICA account.
*/
import { VowShape } from '@agoric/vow';
import { M } from '@endo/patterns';
import { prepareCombineInvitationMakers } from '../exos/combine-invitation-makers.js';
import { withOrchestration } from '../utils/start-helper.js';
import { CosmosOrchestrationInvitationMakersInterface } from '../exos/cosmos-orchestration-account.js';
import * as flows from './continuing-invitation.flows.js';

/**
* @import {GuestInterface} from '@agoric/async-flow';
* @import {Delegation} from '@agoric/cosmic-proto/cosmos/staking/v1beta1/staking.js';
* @import {ContinuingOfferResult} from '@agoric/smart-wallet/src/types.js';
* @import {TimerService} from '@agoric/time';
* @import {LocalChain} from '@agoric/vats/src/localchain.js';
* @import {NameHub} from '@agoric/vats';
* @import {Vow} from '@agoric/vow';
* @import {Remote} from '@agoric/internal';
* @import {Zone} from '@agoric/zone';
* @import {CosmosInterchainService} from '../exos/cosmos-interchain-service.js';
* @import {OrchestrationTools} from '../utils/start-helper.js';
* @import {CosmosOrchestrationAccount} from '../exos/cosmos-orchestration-account.js';
*/

const emptyOfferShape = harden({
// Nothing to give; the funds are deposited offline
give: {},
want: {}, // UNTIL https://github.com/Agoric/agoric-sdk/issues/2230
exit: M.any(),
});

/**
* Orchestration contract to be wrapped by withOrchestration for Zoe.
*
* @param {ZCF} zcf
* @param {{
* agoricNames: Remote<NameHub>;
* localchain: Remote<LocalChain>;
* orchestrationService: Remote<CosmosInterchainService>;
* storageNode: Remote<StorageNode>;
* marshaller: Marshaller;
* timerService: Remote<TimerService>;
* }} privateArgs
* @param {Zone} zone
* @param {OrchestrationTools} tools
*/
const contract = async (
zcf,
privateArgs,
zone,
{ orchestrateAll, vowTools },
) => {
const ExtraInvitationMakerInterface = M.interface('', {
UndelegateAndTransfer: M.call(M.array()).returns(VowShape),
});
/** @type {any} XXX async membrane */
const makeExtraInvitationMaker = zone.exoClass(
'ContinuingInvitationExampleInvitationMakers',
ExtraInvitationMakerInterface,
/** @param {GuestInterface<CosmosOrchestrationAccount>} account */
account => {
return { account };
},
{
/**
* @param {Omit<Delegation, 'delegatorAddress'>[]} delegations
*/
UndelegateAndTransfer(delegations) {
const { account } = this.state;

const invP = zcf.makeInvitation(
// eslint-disable-next-line no-use-before-define -- defined by orchestrateAll, necessarily after this
() => orchFns.undelegateAndTransfer(account, delegations),
'Undelegate and transfer',
undefined,
emptyOfferShape,
);

return vowTools.watch(invP);
},
},
);

/** @type {any} XXX async membrane */
const makeCombineInvitationMakers = prepareCombineInvitationMakers(
zone,
CosmosOrchestrationInvitationMakersInterface,
ExtraInvitationMakerInterface,
);

const orchFns = orchestrateAll(flows, {
makeCombineInvitationMakers,
makeExtraInvitationMaker,
flows,
zcf,
});

const publicFacet = zone.exo('publicFacet', undefined, {
makeAccount() {
return zcf.makeInvitation(
orchFns.makeAccount,
'Make an ICA account',
undefined,
emptyOfferShape,
);
},
});

return harden({ publicFacet });
};

export const start = withOrchestration(contract);
harden(start);
58 changes: 58 additions & 0 deletions packages/orchestration/src/examples/continuing-invitation.flows.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/**
* @import {GuestInterface} from '@agoric/async-flow';
* @import {Orchestrator, OrchestrationFlow, OrchestrationAccount, OrchestrationAccountI, StakingAccountActions} from '../types.js'
* @import {ContinuingOfferResult, InvitationMakers} from '@agoric/smart-wallet/src/types.js';
* @import {MakeCombineInvitationMakers} from '../exos/combine-invitation-makers.js';
* @import {Delegation} from '@agoric/cosmic-proto/cosmos/staking/v1beta1/staking.js';
* @import {CosmosOrchestrationAccount} from '../exos/cosmos-orchestration-account.js';
*/

/**
* @satisfies {OrchestrationFlow}
* @param {Orchestrator} orch
* @param {{
* makeCombineInvitationMakers: MakeCombineInvitationMakers;
* makeExtraInvitationMaker: (account: any) => InvitationMakers;
* }} ctx
* @param {ZCFSeat} _seat
* @param {{ chainName: string }} offerArgs
* @returns {Promise<ContinuingOfferResult>}
*/
export const makeAccount = async (orch, ctx, _seat, { chainName }) => {
const chain = await orch.getChain(chainName);
const account = await chain.makeAccount();

const extraMakers = ctx.makeExtraInvitationMaker(account);

/** @type {ContinuingOfferResult} */
const result = await account.asContinuingOffer();

return {
...result,
invitationMakers: ctx.makeCombineInvitationMakers(
extraMakers,
result.invitationMakers,
),
};
};
harden(makeAccount);

/**
* @satisfies {OrchestrationFlow}
* @param {Orchestrator} orch
* @param {object} ctx
* @param {GuestInterface<CosmosOrchestrationAccount>} account
* @param {Omit<Delegation, 'delegatorAddress'>[]} delegations
* @returns {Promise<string>}
*/
export const undelegateAndTransfer = async (
orch,
ctx,
account,
delegations,
) => {
await account.undelegate(delegations);
// TODO transfer something
return 'guest undelegateAndTransfer complete';
};
harden(undelegateAndTransfer);
110 changes: 110 additions & 0 deletions packages/orchestration/test/examples/continuing-invitation.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { test } from '@agoric/zoe/tools/prepare-test-env-ava.js';

import { inspectMapStore } from '@agoric/internal/src/testing-utils.js';
import { setUpZoeForTest } from '@agoric/zoe/tools/setup-zoe.js';
import { E } from '@endo/far';
import path from 'path';
import { protoMsgMocks, UNBOND_PERIOD_SECONDS } from '../ibc-mocks.js';
import { commonSetup } from '../supports.js';

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

const contractFile = `${dirname}/../../src/examples/continuing-invitation.contract.js`;
type StartFn =
typeof import('@agoric/orchestration/src/examples/continuing-invitation.contract.js').start;

test('start', async t => {
const {
bootstrap: { timer, vowTools: vt },
brands: { ist },
mocks: { ibcBridge },
commonPrivateArgs,
} = await commonSetup(t);

let contractBaggage;
const { zoe, bundleAndInstall } = await setUpZoeForTest({
setJig: ({ baggage }) => {
contractBaggage = baggage;
},
});
const installation: Installation<StartFn> =
await bundleAndInstall(contractFile);

const { publicFacet } = await E(zoe).startInstance(
installation,
{ Stable: ist.issuer },
{},
commonPrivateArgs,
);

const inv = E(publicFacet).makeAccount();

t.is(
(await E(zoe).getInvitationDetails(inv)).description,
'Make an ICA account',
);

const userSeat = await E(zoe).offer(
inv,
{},
{},
{
chainName: 'osmosis',
},
);

const result = await vt.when(E(userSeat).getOfferResult());
t.like(result, {
publicSubscribers: {
account: {
description: 'Staking Account holder status',
storagePath: 'mockChainStorageRoot.cosmos1test',
},
},
});

// Here the account would get funded through Cosmos native operations.

// Delegate the funds like so, but don't bother executing the offer
// because the balances aren't tracked.
const delegateInv = await E(result.invitationMakers).Delegate(
{ value: '10', encoding: 'bech32', chainId: 'osmosis' },
{
denom: 'osmo',
value: 10n,
},
);
t.like(await E(zoe).getInvitationDetails(delegateInv), {
description: 'Delegate',
});

// Undelegate the funds using the guest flow
ibcBridge.addMockAck(
// observed in console
'eyJ0eXBlIjoxLCJkYXRhIjoiQ2xnS0pTOWpiM050YjNNdWMzUmhhMmx1Wnk1Mk1XSmxkR0V4TGsxeloxVnVaR1ZzWldkaGRHVVNMd29MWTI5emJXOXpNWFJsYzNRU0VtTnZjMjF2YzNaaGJHOXdaWEl4ZEdWemRCb01DZ1YxYjNOdGJ4SURNVEF3IiwibWVtbyI6IiJ9',
protoMsgMocks.undelegate.ack,
);
const undelegateInvVow = await E(
result.invitationMakers,
).UndelegateAndTransfer([
{ validatorAddress: 'cosmosvaloper1test', shares: '100' },
]);
const undelegateInv = await vt.when(undelegateInvVow);
t.like(await E(zoe).getInvitationDetails(undelegateInv), {
description: 'Undelegate and transfer',
});

const undelegateUserSeat = await E(zoe).offer(undelegateInv);

// Wait for the unbonding period
timer.advanceBy(UNBOND_PERIOD_SECONDS * 1000n);

const undelegateResult = await vt.when(
E(undelegateUserSeat).getOfferResult(),
);
t.is(undelegateResult, 'guest undelegateAndTransfer complete');

// snapshot the resulting contract baggage
const tree = inspectMapStore(contractBaggage);
t.snapshot(tree, 'contract baggage after start');
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# Snapshot report for `test/examples/continuing-invitation.test.ts`

The actual snapshot is saved in `continuing-invitation.test.ts.snap`.

Generated by [AVA](https://avajs.dev).

## start

> contract baggage after start
{
'Durable Publish Kit_kindHandle': 'Alleged: kind',
Recorder_kindHandle: 'Alleged: kind',
asyncFlow: {
AdminAsyncFlow_kindHandle: 'Alleged: kind',
AdminAsyncFlow_singleton: 'Alleged: AdminAsyncFlow',
Bijection_kindHandle: 'Alleged: kind',
FunctionUnwrapper_kindHandle: 'Alleged: kind',
FunctionUnwrapper_singleton: 'Alleged: FunctionUnwrapper',
LogStore_kindHandle: 'Alleged: kind',
StateUnwrapper_kindHandle: 'Alleged: kind',
asyncFuncEagerWakers: [],
asyncFuncFailures: {},
flowForOutcomeVow: {},
unwrapMap: 'Alleged: weakMapStore',
},
contract: {
CombinedInvitationMakers_kindHandle: 'Alleged: kind',
ContinuingInvitationExampleInvitationMakers_kindHandle: 'Alleged: kind',
orchestration: {
makeAccount: {
asyncFlow_kindHandle: 'Alleged: kind',
endowments: {
0: {
flows: {
makeAccount_kindHandle: 'Alleged: kind',
makeAccount_singleton: 'Alleged: makeAccount',
undelegateAndTransfer_kindHandle: 'Alleged: kind',
undelegateAndTransfer_singleton: 'Alleged: undelegateAndTransfer',
},
makeCombineInvitationMakers_kindHandle: 'Alleged: kind',
makeCombineInvitationMakers_singleton: 'Alleged: makeCombineInvitationMakers',
makeExtraInvitationMaker_kindHandle: 'Alleged: kind',
makeExtraInvitationMaker_singleton: 'Alleged: makeExtraInvitationMaker',
},
},
},
undelegateAndTransfer: {
asyncFlow_kindHandle: 'Alleged: kind',
endowments: {
0: {
flows: {
makeAccount_kindHandle: 'Alleged: kind',
makeAccount_singleton: 'Alleged: makeAccount',
undelegateAndTransfer_kindHandle: 'Alleged: kind',
undelegateAndTransfer_singleton: 'Alleged: undelegateAndTransfer',
},
makeCombineInvitationMakers_kindHandle: 'Alleged: kind',
makeCombineInvitationMakers_singleton: 'Alleged: makeCombineInvitationMakers',
makeExtraInvitationMaker_kindHandle: 'Alleged: kind',
makeExtraInvitationMaker_singleton: 'Alleged: makeExtraInvitationMaker',
},
},
},
},
publicFacet_kindHandle: 'Alleged: kind',
publicFacet_singleton: 'Alleged: publicFacet',
},
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',
chainName: {
osmosis: 'Alleged: RemoteChainFacade public',
},
ibcTools: {
IBCTransferSenderKit_kindHandle: 'Alleged: kind',
ibcResultWatcher_kindHandle: 'Alleged: kind',
ibcResultWatcher_singleton: 'Alleged: ibcResultWatcher',
},
packetTools: {
PacketToolsKit_kindHandle: 'Alleged: kind',
},
},
vows: {
PromiseWatcher_kindHandle: 'Alleged: kind',
VowInternalsKit_kindHandle: 'Alleged: kind',
WatchUtils_kindHandle: 'Alleged: kind',
},
zoe: {},
}
Binary file not shown.

0 comments on commit 5b70d25

Please sign in to comment.