Skip to content

Commit

Permalink
misc Orch factoring improvements (Agoric#9676)
Browse files Browse the repository at this point in the history
refs: Agoric#9449

## Description
Adapted from Agoric#9657 . The `retriable` helper and `withOrchestration` refactor landed already.

This has the rest:
- orchestrateAll helper
- separate module for flows
- ZoeTools endowment (for `withdrawFromSeat` à la Agoric#9449 ) 
- makeOrchestrator abstraction


### Security Considerations
`orchestrateAll` grants the same context to each orchFn. If they have to be different, the author can make multiple calls grouping what is the same.

Improves POLA of some Orchestration services by passing a `makeOrchestrator`.

### Scaling Considerations
none

### Documentation Considerations
none yet

### Testing Considerations
CI

### Upgrade Considerations
not yet deployed
  • Loading branch information
mergify[bot] authored Jul 10, 2024
2 parents 0006934 + ecb67b1 commit 0b6d5f3
Show file tree
Hide file tree
Showing 12 changed files with 289 additions and 168 deletions.
92 changes: 25 additions & 67 deletions packages/orchestration/src/examples/sendAnywhere.contract.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import { makeStateRecord } from '@agoric/async-flow';
import { AmountShape } from '@agoric/ertp';
import { heapVowE } from '@agoric/vow/vat.js';
import { withdrawFromSeat } from '@agoric/zoe/src/contractSupport/zoeHelpers.js';
import { InvitationShape } from '@agoric/zoe/src/typeGuards.js';
import { Fail } from '@endo/errors';
import { E } from '@endo/far';
import { M, mustMatch } from '@endo/patterns';
import { makeResumableAgoricNamesHack } from '../exos/agoric-names-tools.js';
import { M } from '@endo/patterns';
import { CosmosChainInfoShape } from '../typeGuards.js';
import { withOrchestration } from '../utils/start-helper.js';

const { entries } = Object;
import { orchestrationFns } from './sendAnywhereFlows.js';

/**
* @import {TimerService} from '@agoric/time';
Expand All @@ -19,9 +17,7 @@ const { entries } = Object;
* @import {Zone} from '@agoric/zone';
* @import {CosmosChainInfo, IBCConnectionInfo} from '../cosmos-api';
* @import {CosmosInterchainService} from '../exos/cosmos-interchain-service.js';
* @import {Orchestrator} from '../types.js'
* @import {OrchestrationTools} from '../utils/start-helper.js';
* @import {OrchestrationAccount} from '../orchestration-api.js'
*/

/**
Expand All @@ -34,54 +30,6 @@ const { entries } = Object;
* }} OrchestrationPowers
*/

/**
* @param {Orchestrator} orch
* @param {object} ctx
* @param {ZCF} ctx.zcf
* @param {any} ctx.agoricNamesTools TODO Give this a better type
* @param {{ account: OrchestrationAccount<any> | undefined }} ctx.contractState
* @param {ZCFSeat} seat
* @param {object} offerArgs
* @param {string} offerArgs.chainName
* @param {string} offerArgs.destAddr
*/
const sendItFn = async (
orch,
{ zcf, agoricNamesTools, contractState },
seat,
offerArgs,
) => {
mustMatch(offerArgs, harden({ chainName: M.scalar(), destAddr: M.string() }));
const { chainName, destAddr } = offerArgs;
const { give } = seat.getProposal();
const [[kw, amt]] = entries(give);
const { denom } = await agoricNamesTools.findBrandInVBank(amt.brand);
const chain = await orch.getChain(chainName);

if (!contractState.account) {
const agoricChain = await orch.getChain('agoric');
contractState.account = await agoricChain.makeAccount();
console.log('contractState.account', contractState.account);
}

const info = await chain.getChainInfo();
console.log('info', info);
const { chainId } = info;
assert(typeof chainId === 'string', 'bad chainId');
const { [kw]: pmtP } = await withdrawFromSeat(zcf, seat, give);
// #9212 types for chain account helpers
// @ts-expect-error LCA should have .deposit() method
await E.when(pmtP, pmt => contractState.account?.deposit(pmt));
await contractState.account?.transfer(
{ denom, value: amt.value },
{
value: destAddr,
encoding: 'bech32',
chainId,
},
);
};

export const SingleAmountRecord = M.and(
M.recordOf(M.string(), AmountShape, {
numPropertiesLimit: 1,
Expand All @@ -103,26 +51,36 @@ const contract = async (
zcf,
privateArgs,
zone,
{ chainHub, orchestrate, vowTools },
{ chainHub, orchestrateAll, vowTools, zoeTools },
) => {
const agoricNamesTools = makeResumableAgoricNamesHack(zone, {
agoricNames: privateArgs.agoricNames,
vowTools,
});

const contractState = makeStateRecord(
/** @type {{ account: OrchestrationAccount<any> | undefined }} */ {
account: undefined,
},
);

/** @type {OfferHandler} */
const sendIt = orchestrate(
'sendIt',
{ zcf, agoricNamesTools, contractState },
sendItFn,
// TODO should be a provided helper
const findBrandInVBank = vowTools.retriable(
zone,
'findBrandInVBank',
/** @param {Brand} brand */
async brand => {
const { agoricNames } = privateArgs;
const assets = await E(E(agoricNames).lookup('vbankAsset')).values();
const it = assets.find(a => a.brand === brand);
it || Fail`brand ${brand} not in agoricNames.vbankAsset`;
return it;
},
);

// orchestrate uses the names on orchestrationFns to do a "prepare" of the associated behavior
const orchFns = orchestrateAll(orchestrationFns, {
zcf,
contractState,
localTransfer: zoeTools.localTransfer,
findBrandInVBank,
});

const publicFacet = zone.exo(
'Send PF',
M.interface('Send PF', {
Expand All @@ -131,7 +89,7 @@ const contract = async (
{
makeSendInvitation() {
return zcf.makeInvitation(
sendIt,
orchFns.sendIt,
'send',
undefined,
M.splitRecord({ give: SingleAmountRecord }),
Expand Down
59 changes: 59 additions & 0 deletions packages/orchestration/src/examples/sendAnywhereFlows.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { M, mustMatch } from '@endo/patterns';

/**
* @import {Orchestrator, OrchestrationAccount} from '../types.js';
*/

const { entries } = Object;

// in guest file (the orchestration functions)
// the second argument is all the endowments provided

export const orchestrationFns = harden({
/**
* @param {Orchestrator} orch
* @param {object} ctx
* @param {{ account: OrchestrationAccount<any> }} ctx.contractState
* @param {any} ctx.localTransfer
* @param {any} ctx.findBrandInVBank
* @param {ZCFSeat} seat
* @param {{ chainName: string; destAddr: string }} offerArgs
*/
async sendIt(
orch,
{ contractState, localTransfer, findBrandInVBank },
seat,
offerArgs,
) {
mustMatch(
offerArgs,
harden({ chainName: M.scalar(), destAddr: M.string() }),
);
const { chainName, destAddr } = offerArgs;
// NOTE the proposal shape ensures that the `give` is a single asset
const { give } = seat.getProposal();
const [[_kw, amt]] = entries(give);
const { denom } = await findBrandInVBank(amt.brand);
const chain = await orch.getChain(chainName);

if (!contractState.account) {
const agoricChain = await orch.getChain('agoric');
contractState.account = await agoricChain.makeAccount();
}

const info = await chain.getChainInfo();
const { chainId } = info;
assert(typeof chainId === 'string', 'bad chainId');

await localTransfer(seat, contractState.account, give);

await contractState.account.transfer(
{ denom, value: amt.value },
{
value: destAddr,
encoding: 'bech32',
chainId,
},
);
},
});
28 changes: 13 additions & 15 deletions packages/orchestration/src/examples/swapExample.contract.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import { StorageNodeShape } from '@agoric/internal';
import { TimerServiceShape } from '@agoric/time';
import { withdrawFromSeat } from '@agoric/zoe/src/contractSupport/zoeHelpers.js';
import { deeplyFulfilled } from '@endo/marshal';
import { M, objectMap } from '@endo/patterns';
import { M } from '@endo/patterns';
import { orcUtils } from '../utils/orc.js';
import { withOrchestration } from '../utils/start-helper.js';

/**
* @import {Orchestrator, IcaAccount, CosmosValidatorAddress} from '../types.js'
* @import {Orchestrator, CosmosValidatorAddress} from '../types.js'
* @import {TimerService} from '@agoric/time';
* @import {LocalChain} from '@agoric/vats/src/localchain.js';
* @import {Remote} from '@agoric/internal';
Expand All @@ -20,13 +18,13 @@ import { withOrchestration } from '../utils/start-helper.js';
/**
* @param {Orchestrator} orch
* @param {object} ctx
* @param {ZCF} ctx.zcf
* @param {OrchestrationTools['zoeTools']['localTransfer']} ctx.localTransfer
* @param {ZCFSeat} seat
* @param {object} offerArgs
* @param {Amount<'nat'>} offerArgs.staked
* @param {CosmosValidatorAddress} offerArgs.validator
*/
const stackAndSwapFn = async (orch, { zcf }, seat, offerArgs) => {
const stackAndSwapFn = async (orch, { localTransfer }, seat, offerArgs) => {
const { give } = seat.getProposal();

const omni = await orch.getChain('omniflixhub');
Expand All @@ -40,13 +38,9 @@ const stackAndSwapFn = async (orch, { zcf }, seat, offerArgs) => {
const omniAddress = omniAccount.getAddress();

// deposit funds from user seat to LocalChainAccount
const payments = await withdrawFromSeat(zcf, seat, give);
await deeplyFulfilled(
objectMap(payments, payment =>
// @ts-expect-error payment is ERef<Payment> which happens to work but isn't officially supported
localAccount.deposit(payment),
),
);
// TODO localTransfer type returns vow but in the guest context it should be a promise
// @ts-expect-error XXX localAccount type
await localTransfer(seat, localAccount, give);
seat.exit();

// build swap instructions with orcUtils library
Expand Down Expand Up @@ -104,7 +98,7 @@ export const makeNatAmountShape = (brand, min) =>
* @param {Zone} zone
* @param {OrchestrationTools} tools
*/
const contract = async (zcf, privateArgs, zone, { orchestrate }) => {
const contract = async (zcf, privateArgs, zone, { orchestrate, zoeTools }) => {
const { brands } = zcf.getTerms();

/** deprecated historical example */
Expand All @@ -114,7 +108,11 @@ const contract = async (zcf, privateArgs, zone, { orchestrate }) => {
* { staked: Amount<'nat'>; validator: CosmosValidatorAddress }
* >}
*/
const swapAndStakeHandler = orchestrate('LSTTia', { zcf }, stackAndSwapFn);
const swapAndStakeHandler = orchestrate(
'LSTTia',
{ zcf, localTransfer: zoeTools.localTransfer },
stackAndSwapFn,
);

const publicFacet = zone.exo('publicFacet', undefined, {
makeSwapAndStakeInvitation() {
Expand Down
8 changes: 8 additions & 0 deletions packages/orchestration/src/exos/orchestrator.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,3 +139,11 @@ export const prepareOrchestratorKit = (
},
);
harden(prepareOrchestratorKit);
/**
* Host side of the Orchestrator interface. (Methods return vows instead of
* promises as the interface within the guest function.)
*
* @typedef {ReturnType<
* ReturnType<typeof prepareOrchestratorKit>
* >['orchestrator']} HostOrchestrator
*/
Loading

0 comments on commit 0b6d5f3

Please sign in to comment.