Skip to content

Commit

Permalink
9796 continuing ica (#10023)
Browse files Browse the repository at this point in the history
closes: #9796

## Description
Demonstrates a pattern for creating continuing invitations that have handlers written as orchestration flows.

Builds upon the combine-invitation-makers from #9821

### Security Considerations
None

### Scaling Considerations
Augmenting an existing exo's invitationMaker requires two new ones: the exo holding the other makers and an exo to wrap them both.

### Documentation Considerations
Once this lands it should have an explicit tutorial for developers needing this functionality.

### Testing Considerations
new tests

### Upgrade Considerations
not yet deployed
  • Loading branch information
mergify[bot] authored Sep 6, 2024
2 parents d6f50e3 + 2689372 commit 1976c50
Show file tree
Hide file tree
Showing 8 changed files with 550 additions and 21 deletions.
138 changes: 138 additions & 0 deletions packages/orchestration/src/examples/staking-combinations.contract.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/**
* @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 { AmountShape } from '@agoric/ertp';
import { VowShape } from '@agoric/vow';
import { M } from '@endo/patterns';
import { prepareCombineInvitationMakers } from '../exos/combine-invitation-makers.js';
import { CosmosOrchestrationInvitationMakersInterface } from '../exos/cosmos-orchestration-account.js';
import { withOrchestration } from '../utils/start-helper.js';
import * as flows from './staking-combinations.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('', {
DepositAndDelegate: M.call(M.array()).returns(VowShape),
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 };
},
{
DepositAndDelegate() {
const { account } = this.state;

const invP = zcf.makeInvitation(
(seat, validatorAddr, amountArg) =>
// eslint-disable-next-line no-use-before-define -- defined by orchestrateAll, necessarily after this
orchFns.depositAndDelegate(account, seat, validatorAddr, amountArg),
'Deposit and delegate',
undefined,
{
give: {
Stake: AmountShape,
},
},
);

return vowTools.watch(invP);
},
/**
* @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);
83 changes: 83 additions & 0 deletions packages/orchestration/src/examples/staking-combinations.flows.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/**
* @import {GuestInterface} from '@agoric/async-flow';
* @import {Orchestrator, OrchestrationFlow, OrchestrationAccount, OrchestrationAccountI, StakingAccountActions, AmountArg, CosmosValidatorAddress} 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 {ZCFSeat} seat
* @param {CosmosValidatorAddress} validator
* @param {AmountArg} amount
* @returns {Promise<string>}
*/
export const depositAndDelegate = async (
orch,
ctx,
account,
seat,
validator,
amount,
) => {
console.log('depositAndDelegate', account, seat, validator, amount);
// TODO deposit the amount
await account.delegate(validator, amount);
return 'guest depositAndDelegate complete';
};
harden(depositAndDelegate);

/**
* @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);
53 changes: 53 additions & 0 deletions packages/orchestration/src/exos/combine-invitation-makers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { M } from '@endo/patterns';
import {
prepareGuardedAttenuator,
makeSyncMethodCallback,
} from '@agoric/internal/src/callback.js';
import { getMethodNames } from '@agoric/internal';

/**
* @import {InvitationMakers} from '@agoric/smart-wallet/src/types.js';
* @import {Zone} from '@agoric/zone';
*/

// TODO use a helper from Endo https://github.com/endojs/endo/issues/2448
/**
* Takes two or more InvitationMaker exos and combines them into a new one.
*
* @param {Zone} zone
* @param {import('@endo/patterns').InterfaceGuard[]} interfaceGuards
*/
export const prepareCombineInvitationMakers = (zone, ...interfaceGuards) => {
const methodGuards = interfaceGuards.map(ig => ig.payload.methodGuards);
const CombinedInterfaceGuard = M.interface(
'CombinedInvitationMakers interface',
Object.assign({}, ...methodGuards),
);

const mixin = prepareGuardedAttenuator(zone, CombinedInterfaceGuard, {
tag: 'CombinedInvitationMakers',
});

/**
* @template {InvitationMakers[]} IM
* @param {IM} invitationMakers
* @returns {IM[number]}
*/
const combineInvitationMakers = (...invitationMakers) => {
const overrides = {};
for (const invMakers of invitationMakers) {
// remove '__getInterfaceGuard__', '__getMethodNames__'
const names = getMethodNames(invMakers).filter(n => !n.startsWith('__'));
for (const key of names) {
overrides[key] = makeSyncMethodCallback(invMakers, key);
}
}
return mixin({
overrides,
});
};

return combineInvitationMakers;
};

/** @typedef {ReturnType<typeof prepareCombineInvitationMakers>} MakeCombineInvitationMakers */
40 changes: 22 additions & 18 deletions packages/orchestration/src/exos/cosmos-orchestration-account.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,27 @@ const PUBLIC_TOPICS = {
account: ['Staking Account holder status', M.any()],
};

export const CosmosOrchestrationInvitationMakersInterface = M.interface(
'invitationMakers',
{
Delegate: M.call(ChainAddressShape, AmountArgShape).returns(M.promise()),
Redelegate: M.call(
ChainAddressShape,
ChainAddressShape,
AmountArgShape,
).returns(M.promise()),
WithdrawReward: M.call(ChainAddressShape).returns(M.promise()),
Undelegate: M.call(M.arrayOf(DelegationShape)).returns(M.promise()),
DeactivateAccount: M.call().returns(M.promise()),
ReactivateAccount: M.call().returns(M.promise()),
TransferAccount: M.call().returns(M.promise()),
Send: M.call().returns(M.promise()),
SendAll: M.call().returns(M.promise()),
Transfer: M.call().returns(M.promise()),
},
);
harden(CosmosOrchestrationInvitationMakersInterface);

/**
* @param {Zone} zone
* @param {object} powers
Expand Down Expand Up @@ -177,24 +198,7 @@ export const prepareCosmosOrchestrationAccountKit = (
.returns(Vow$(M.record())),
}),
holder: IcaAccountHolderI,
invitationMakers: M.interface('invitationMakers', {
Delegate: M.call(ChainAddressShape, AmountArgShape).returns(
M.promise(),
),
Redelegate: M.call(
ChainAddressShape,
ChainAddressShape,
AmountArgShape,
).returns(M.promise()),
WithdrawReward: M.call(ChainAddressShape).returns(M.promise()),
Undelegate: M.call(M.arrayOf(DelegationShape)).returns(M.promise()),
DeactivateAccount: M.call().returns(M.promise()),
ReactivateAccount: M.call().returns(M.promise()),
TransferAccount: M.call().returns(M.promise()),
Send: M.call().returns(M.promise()),
SendAll: M.call().returns(M.promise()),
Transfer: M.call().returns(M.promise()),
}),
invitationMakers: CosmosOrchestrationInvitationMakersInterface,
},
/**
* @param {object} info
Expand Down
Loading

0 comments on commit 1976c50

Please sign in to comment.