From 822922cb285d7765443d57fa4286668880df9f98 Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Fri, 30 Aug 2024 10:59:05 -0700 Subject: [PATCH 01/10] chore: remove 'Example' from example contract names --- packages/orchestration/src/examples/README.md | 4 ++-- ...{swapExample.contract.js => swap.contract.js} | 0 ...ondExample.contract.js => unbond.contract.js} | 0 ...ple.test.ts.md => unbond.contract.test.ts.md} | 6 +++--- .../snapshots/unbond.contract.test.ts.snap | Bin 0 -> 1000 bytes .../snapshots/unbondExample.test.ts.snap | Bin 985 -> 0 bytes ...swapExample.test.ts => swap.contract.test.ts} | 4 ++-- ...ndExample.test.ts => unbond.contract.test.ts} | 4 ++-- 8 files changed, 9 insertions(+), 9 deletions(-) rename packages/orchestration/src/examples/{swapExample.contract.js => swap.contract.js} (100%) rename packages/orchestration/src/examples/{unbondExample.contract.js => unbond.contract.js} (100%) rename packages/orchestration/test/examples/snapshots/{unbondExample.test.ts.md => unbond.contract.test.ts.md} (92%) create mode 100644 packages/orchestration/test/examples/snapshots/unbond.contract.test.ts.snap delete mode 100644 packages/orchestration/test/examples/snapshots/unbondExample.test.ts.snap rename packages/orchestration/test/examples/{swapExample.test.ts => swap.contract.test.ts} (91%) rename packages/orchestration/test/examples/{unbondExample.test.ts => unbond.contract.test.ts} (89%) diff --git a/packages/orchestration/src/examples/README.md b/packages/orchestration/src/examples/README.md index a9dac0b0259..33623c46adc 100644 --- a/packages/orchestration/src/examples/README.md +++ b/packages/orchestration/src/examples/README.md @@ -11,7 +11,7 @@ This directory contains sample contracts showcasing the Orchestration API. Each The following contracts are a work in progress as they contain bindings that need to be promptly updated. - **stakeIca.contract.js**: Interchain account creation for remote staking -- **unbondExample.contract.js**: Cross-chain unbonding and liquid staking -- **swapExample.contract.js**: Token swapping and remote staking +- **unbond.contract.js**: Cross-chain unbonding and liquid staking +- **swap.contract.js**: Token swapping and remote staking - **stakeBld.contract.js**: BLD token staking on Agoric diff --git a/packages/orchestration/src/examples/swapExample.contract.js b/packages/orchestration/src/examples/swap.contract.js similarity index 100% rename from packages/orchestration/src/examples/swapExample.contract.js rename to packages/orchestration/src/examples/swap.contract.js diff --git a/packages/orchestration/src/examples/unbondExample.contract.js b/packages/orchestration/src/examples/unbond.contract.js similarity index 100% rename from packages/orchestration/src/examples/unbondExample.contract.js rename to packages/orchestration/src/examples/unbond.contract.js diff --git a/packages/orchestration/test/examples/snapshots/unbondExample.test.ts.md b/packages/orchestration/test/examples/snapshots/unbond.contract.test.ts.md similarity index 92% rename from packages/orchestration/test/examples/snapshots/unbondExample.test.ts.md rename to packages/orchestration/test/examples/snapshots/unbond.contract.test.ts.md index 98c8b7a6295..7cfd1ffe8da 100644 --- a/packages/orchestration/test/examples/snapshots/unbondExample.test.ts.md +++ b/packages/orchestration/test/examples/snapshots/unbond.contract.test.ts.md @@ -1,6 +1,6 @@ -# Snapshot report for `test/examples/unbondExample.test.ts` +# Snapshot report for `test/examples/unbond.contract.test.ts` -The actual snapshot is saved in `unbondExample.test.ts.snap`. +The actual snapshot is saved in `unbond.contract.test.ts.snap`. Generated by [AVA](https://avajs.dev). @@ -26,7 +26,7 @@ Generated by [AVA](https://avajs.dev). }, contract: { orchestration: { - LSTTia: { + unbondAndLiquidStake: { asyncFlow_kindHandle: 'Alleged: kind', }, }, diff --git a/packages/orchestration/test/examples/snapshots/unbond.contract.test.ts.snap b/packages/orchestration/test/examples/snapshots/unbond.contract.test.ts.snap new file mode 100644 index 0000000000000000000000000000000000000000..34c192aeb28758f92e409468354cc514965f4311 GIT binary patch literal 1000 zcmV3RzVR!xuFL>PX?b`m?={nGAIw%tOCgb&iggLm{zF~f~uEHP9Yo=7@Ky2>OE9>p;=^!=rmT+}f7h)?_$fPDa; z0{9ug9{~OZaDf1C5MYB;*Gcst{D?({km>K*+8W3*NDX8a8^qZpE=Xmayzow{X((9Z zdKwCzbQ@Q>x!vP3-lH-WY(iUZ3c)%oeycIUERBsr7AYMwy@25gA$tUvzyVF}%cv#P zFoQnNL4Ks9(KIp{@?#EJRyyjk#As@`k{KL0GuXSUhDo;iW^UE}Sg}z6&m23z2M+Ls z1N`m)7hK@B3w-MWf4acS9&ptIqz63otbjW60riRZlL!3b0Z%;O)rv*0+Vr4T0q#_Q zA1c6B6*#T}pH+chtH3{1pkA{ig2Ilk)PUD&fT#iA*z{$KK98;x>S4*s<#sdfbJ?6^ zrEDoYZ2dC8S5>%mSdm6_p9In;D8Sg0!J6wVhF3dcA% z6)2GH=Ga%ABcn7c>&Etxp@x+?#R?&hYqN^q9QRf#qjzbC>07kN^tdlvf5uv$vRagj zRI@SVVxH0#YS>cxAT^Qdv*SXxf;98;8XZhlwx+HP8SP!8gDeDD#kraO+y1lrc~1Me z!Dui=EgG@XgJ~;oTgc(&)HFxST=XrS*LPLYS4ra_?`NYKMJko1v8Tjhvu{rG(!PqQ zc!uODhVc1JZ?{XiY|)6urR(kN%oSDoG&_f^uM8`<{R$!NTp0w>$of6nXBqS`2l+gV zZYU+jwfTJxSsKu&$IJ;hF0c7Ex~^59C+rqAQMY`W&K;}a6=|53R3xWJ%T~7UO~Xa9 zfa%G&t~9l#_lZ0CTU}i(jB@XP2Yto9HJryj@Wi)ni<(VyHv(Wg06q*XYQ1m|eHj2> z2f*I}aH($5mkJKttplIfv@?YZiDW7MuAZA?sSK5jn=;<#cTyf78QL3<62}VmlIly| z7W`f}4JT_Cry(ws=tuUy`}p{Y#TOdl~BGkuz#=3#Wml2n+} zR&PO|Fa@`#_BMBt4y&DJ{kGVNeB%682mZ2C?>{Z|^N#?!@H2!78ZvQjXYyujgtXJ4 W9oC?2!*nBiS^ozBnc<{x3jhHBD&ac- literal 0 HcmV?d00001 diff --git a/packages/orchestration/test/examples/snapshots/unbondExample.test.ts.snap b/packages/orchestration/test/examples/snapshots/unbondExample.test.ts.snap deleted file mode 100644 index 92e7e65fe769a934f23e85bac107ae2ea2e2e76f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 985 zcmV;~119`IRzVj4ppS zjrkE2KOc(-00000000A>R!wgkMHqf&cfH=Vn=i$oq)ov+AVej&a6w#<8?aI}B~g;5 zLPDy>vtxVf@r;?B#Vxn~gH}jz;6%^#KX3uTg%e!h2%@J-`~rl$j_vhqi|p)aHP7=t z^UnJ<^KH8&6ze4qpKuc!!Fgh+HgO{9An7WTJbV&G)X-0=H5X-!J`#|i31A<@+pe}ni3t$UmqLZ-j#>+2vZAZ3s>Y!atNT#(WRdF{hg)0W`%!?Y#h zq+7oko4dVOMtf97f=_5G4Iy}kNAK51n5D6K#F^3&*9#b~60%2t2^`Yofn-ggh8gsA z4hoo(MpI@o zE%PLn9l?!~6T-!*^*qzksa=mpUdBo;>%mSlm6_pfIn;D8Sg0!J6fPGP3dguK6)2GH z=Ga%AW1}=*){X6BLk(Z%6sv?hE6*yvHtwxdvX5wo>pQf^^|&wGV8+^*vYIp&spezK z)jXvw)Uc`aL28)l^V34M!Zh>p79C7hwx+HPIqlt|gDeDD#kraO2f@qxc~1M4!DukC zCS`o-!L*gPE#$B^HO65`&n-=rc!C@drB-e`{p#S?JGvbOC--R zgfC}$yImT~CS^2Qy526%Tv1D(X6K0amEp^6ze-3uR|X*)S$|CXJcAzRpdfCs6Q#ts zHowmy)d6KaZqC4QdCj-cp;rAk;diKE-R0AC=~NAGNW-as_M(QE#mv#v^80~It^-}D9<|G z67dtGcP6jIdW&{Cw8QJPZMd#yZ|VO4Mo@Eb HVhaEO_uSYE diff --git a/packages/orchestration/test/examples/swapExample.test.ts b/packages/orchestration/test/examples/swap.contract.test.ts similarity index 91% rename from packages/orchestration/test/examples/swapExample.test.ts rename to packages/orchestration/test/examples/swap.contract.test.ts index 2c59723510b..7eb8869eb38 100644 --- a/packages/orchestration/test/examples/swapExample.test.ts +++ b/packages/orchestration/test/examples/swap.contract.test.ts @@ -8,9 +8,9 @@ import { commonSetup } from '../supports.js'; const dirname = path.dirname(new URL(import.meta.url).pathname); -const contractFile = `${dirname}/../../src/examples/swapExample.contract.js`; +const contractFile = `${dirname}/../../src/examples/swap.contract.js`; type StartFn = - typeof import('@agoric/orchestration/src/examples/swapExample.contract.js').start; + typeof import('@agoric/orchestration/src/examples/swap.contract.js').start; test('start', async t => { const { diff --git a/packages/orchestration/test/examples/unbondExample.test.ts b/packages/orchestration/test/examples/unbond.contract.test.ts similarity index 89% rename from packages/orchestration/test/examples/unbondExample.test.ts rename to packages/orchestration/test/examples/unbond.contract.test.ts index 830353380f4..74266f0b62c 100644 --- a/packages/orchestration/test/examples/unbondExample.test.ts +++ b/packages/orchestration/test/examples/unbond.contract.test.ts @@ -8,9 +8,9 @@ import { commonSetup } from '../supports.js'; const dirname = path.dirname(new URL(import.meta.url).pathname); -const contractFile = `${dirname}/../../src/examples/unbondExample.contract.js`; +const contractFile = `${dirname}/../../src/examples/unbond.contract.js`; type StartFn = - typeof import('@agoric/orchestration/src/examples/unbondExample.contract.js').start; + typeof import('@agoric/orchestration/src/examples/unbond.contract.js').start; test('start', async t => { const { From eb16c19d0af40472a68a2dc7648f2b03d7120fa0 Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Fri, 30 Aug 2024 10:41:42 -0700 Subject: [PATCH 02/10] refactor: extract flows --- .../src/examples/swap.contract.js | 67 ++++--------------- .../orchestration/src/examples/swap.flows.js | 57 ++++++++++++++++ .../src/examples/unbond.contract.js | 41 +----------- .../src/examples/unbond.flows.js | 47 +++++++++++++ 4 files changed, 119 insertions(+), 93 deletions(-) create mode 100644 packages/orchestration/src/examples/swap.flows.js create mode 100644 packages/orchestration/src/examples/unbond.flows.js diff --git a/packages/orchestration/src/examples/swap.contract.js b/packages/orchestration/src/examples/swap.contract.js index 32f5de5c042..b4af770f7b0 100644 --- a/packages/orchestration/src/examples/swap.contract.js +++ b/packages/orchestration/src/examples/swap.contract.js @@ -1,12 +1,10 @@ import { StorageNodeShape } from '@agoric/internal'; import { TimerServiceShape } from '@agoric/time'; import { M } from '@endo/patterns'; -import { orcUtils } from '../utils/orc.js'; import { withOrchestration } from '../utils/start-helper.js'; +import * as flows from './swap.flows.js'; /** - * @import {LocalTransfer} from '../utils/zoe-tools.js'; - * @import {Orchestrator, CosmosValidatorAddress, OrchestrationFlow} from '../types.js' * @import {TimerService} from '@agoric/time'; * @import {LocalChain} from '@agoric/vats/src/localchain.js'; * @import {Remote} from '@agoric/internal'; @@ -16,50 +14,6 @@ import { withOrchestration } from '../utils/start-helper.js'; * @import {OrchestrationTools} from '../utils/start-helper.js'; */ -/** - * @satisfies {OrchestrationFlow} - * @param {Orchestrator} orch - * @param {object} ctx - * @param {LocalTransfer} ctx.localTransfer - * @param {ZCFSeat} seat - * @param {object} offerArgs - * @param {Amount<'nat'>} offerArgs.staked - * @param {CosmosValidatorAddress} offerArgs.validator - */ -const stakeAndSwapFn = async (orch, { localTransfer }, seat, offerArgs) => { - const { give } = seat.getProposal(); - - const omni = await orch.getChain('omniflixhub'); - const agoric = await orch.getChain('agoric'); - - const [omniAccount, localAccount] = await Promise.all([ - omni.makeAccount(), - agoric.makeAccount(), - ]); - - const omniAddress = omniAccount.getAddress(); - - // deposit funds from user seat to LocalChainAccount - await localTransfer(seat, localAccount, give); - seat.exit(); - - // build swap instructions with orcUtils library - const transferMsg = orcUtils.makeOsmosisSwap({ - destChain: 'omniflixhub', - destAddress: omniAddress, - amountIn: give.Stable, - brandOut: /** @type {any} */ ('FIXME'), - slippage: 0.03, - }); - - try { - await localAccount.transferSteps(give.Stable, transferMsg); - await omniAccount.delegate(offerArgs.validator, offerArgs.staked); - } catch (e) { - console.error(e); - } -}; - /** @type {ContractMeta} */ export const meta = { privateArgsShape: { @@ -99,20 +53,23 @@ harden(makeNatAmountShape); * @param {Zone} zone * @param {OrchestrationTools} tools */ -const contract = async (zcf, privateArgs, zone, { orchestrate, zoeTools }) => { +const contract = async ( + zcf, + privateArgs, + zone, + { orchestrateAll, zoeTools }, +) => { const { brands } = zcf.getTerms(); - /** deprecated historical example */ - const swapAndStakeHandler = orchestrate( - 'LSTTia', - { zcf, localTransfer: zoeTools.localTransfer }, - stakeAndSwapFn, - ); + const { stakeAndSwap } = orchestrateAll(flows, { + zcf, + localTransfer: zoeTools.localTransfer, + }); const publicFacet = zone.exo('publicFacet', undefined, { makeSwapAndStakeInvitation() { return zcf.makeInvitation( - swapAndStakeHandler, + stakeAndSwap, 'Swap for TIA and stake', undefined, harden({ diff --git a/packages/orchestration/src/examples/swap.flows.js b/packages/orchestration/src/examples/swap.flows.js new file mode 100644 index 00000000000..7514683440f --- /dev/null +++ b/packages/orchestration/src/examples/swap.flows.js @@ -0,0 +1,57 @@ +import { orcUtils } from '../utils/orc.js'; + +/** + * @import {LocalTransfer} from '../utils/zoe-tools.js'; + * @import {Orchestrator, CosmosValidatorAddress, OrchestrationFlow} from '../types.js' + */ + +// XXX does not actually work. An early illustration that needs to be fixed. +/** + * @satisfies {OrchestrationFlow} + * @param {Orchestrator} orch + * @param {object} ctx + * @param {LocalTransfer} ctx.localTransfer + * @param {ZCFSeat} seat + * @param {object} offerArgs + * @param {Amount<'nat'>} offerArgs.staked + * @param {CosmosValidatorAddress} offerArgs.validator + */ +export const stakeAndSwap = async ( + orch, + { localTransfer }, + seat, + offerArgs, +) => { + const { give } = seat.getProposal(); + + const omni = await orch.getChain('omniflixhub'); + const agoric = await orch.getChain('agoric'); + + const [omniAccount, localAccount] = await Promise.all([ + omni.makeAccount(), + agoric.makeAccount(), + ]); + + const omniAddress = omniAccount.getAddress(); + + // deposit funds from user seat to LocalChainAccount + await localTransfer(seat, localAccount, give); + seat.exit(); + + // build swap instructions with orcUtils library + const transferMsg = orcUtils.makeOsmosisSwap({ + destChain: 'omniflixhub', + destAddress: omniAddress, + amountIn: give.Stable, + brandOut: /** @type {any} */ ('FIXME'), + slippage: 0.03, + }); + + try { + await localAccount.transferSteps(give.Stable, transferMsg); + await omniAccount.delegate(offerArgs.validator, offerArgs.staked); + } catch (e) { + console.error(e); + } +}; +harden(stakeAndSwap); diff --git a/packages/orchestration/src/examples/unbond.contract.js b/packages/orchestration/src/examples/unbond.contract.js index e2d09a1117f..c3768146660 100644 --- a/packages/orchestration/src/examples/unbond.contract.js +++ b/packages/orchestration/src/examples/unbond.contract.js @@ -1,8 +1,8 @@ import { M } from '@endo/patterns'; import { withOrchestration } from '../utils/start-helper.js'; +import * as flows from './unbond.flows.js'; /** - * @import {Orchestrator, OrchestrationFlow} from '../types.js' * @import {TimerService} from '@agoric/time'; * @import {LocalChain} from '@agoric/vats/src/localchain.js'; * @import {NameHub} from '@agoric/vats'; @@ -12,37 +12,6 @@ import { withOrchestration } from '../utils/start-helper.js'; * @import {OrchestrationTools} from '../utils/start-helper.js'; */ -/** - * @satisfies {OrchestrationFlow} - * @param {Orchestrator} orch - * @param {object} ctx - * @param {ZCF} ctx.zcf - * @param {ZCFSeat} _seat - * @param {undefined} _offerArgs - */ -const unbondAndLiquidStakeFn = async (orch, { zcf }, _seat, _offerArgs) => { - console.log('zcf within the membrane', zcf); - // We would actually alreaady have the account from the orchestrator - // ??? could these be passed in? It would reduce the size of this handler, - // keeping it focused on long-running operations. - const omni = await orch.getChain('omniflixhub'); - const omniAccount = await omni.makeAccount(); - - // TODO implement these - // const delegations = await celestiaAccount.getDelegations(); - // // wait for the undelegations to be complete (may take weeks) - // await celestiaAccount.undelegate(delegations); - // ??? should this be synchronous? depends on how names are resolved. - const stride = await orch.getChain('stride'); - const strideAccount = await stride.makeAccount(); - - // TODO the `TIA` string actually needs to be the Brand from AgoricNames - // const tiaAmt = await celestiaAccount.getBalance('TIA'); - // await celestiaAccount.transfer(tiaAmt, strideAccount.getAddress()); - // await strideAccount.liquidStake(tiaAmt); - console.log(omniAccount, strideAccount); -}; - /** * Orchestration contract to be wrapped by withOrchestration for Zoe * @@ -58,12 +27,8 @@ const unbondAndLiquidStakeFn = async (orch, { zcf }, _seat, _offerArgs) => { * @param {Zone} zone * @param {OrchestrationTools} tools */ -const contract = async (zcf, privateArgs, zone, { orchestrate }) => { - const unbondAndLiquidStake = orchestrate( - 'LSTTia', - { zcf }, - unbondAndLiquidStakeFn, - ); +const contract = async (zcf, privateArgs, zone, { orchestrateAll }) => { + const { unbondAndLiquidStake } = orchestrateAll(flows, { zcf }); const publicFacet = zone.exo('publicFacet', undefined, { makeUnbondAndLiquidStakeInvitation() { diff --git a/packages/orchestration/src/examples/unbond.flows.js b/packages/orchestration/src/examples/unbond.flows.js new file mode 100644 index 00000000000..307cdeccea5 --- /dev/null +++ b/packages/orchestration/src/examples/unbond.flows.js @@ -0,0 +1,47 @@ +/** + * @import {Orchestrator, OrchestrationFlow} from '../types.js' + * @import {TimerService} from '@agoric/time'; + * @import {LocalChain} from '@agoric/vats/src/localchain.js'; + * @import {NameHub} from '@agoric/vats'; + * @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'; + */ + +/** + * @satisfies {OrchestrationFlow} + * @param {Orchestrator} orch + * @param {object} ctx + * @param {ZCF} ctx.zcf + * @param {ZCFSeat} _seat + * @param {undefined} _offerArgs + */ +export const unbondAndLiquidStake = async ( + orch, + { zcf }, + _seat, + _offerArgs, +) => { + console.log('zcf within the membrane', zcf); + // We would actually alreaady have the account from the orchestrator + // ??? could these be passed in? It would reduce the size of this handler, + // keeping it focused on long-running operations. + const omni = await orch.getChain('omniflixhub'); + const omniAccount = await omni.makeAccount(); + + // TODO implement these + // const delegations = await celestiaAccount.getDelegations(); + // // wait for the undelegations to be complete (may take weeks) + // await celestiaAccount.undelegate(delegations); + // ??? should this be synchronous? depends on how names are resolved. + const stride = await orch.getChain('stride'); + const strideAccount = await stride.makeAccount(); + + // TODO the `TIA` string actually needs to be the Brand from AgoricNames + // const tiaAmt = await celestiaAccount.getBalance('TIA'); + // await celestiaAccount.transfer(tiaAmt, strideAccount.getAddress()); + // await strideAccount.liquidStake(tiaAmt); + console.log(omniAccount, strideAccount); +}; +harden(unbondAndLiquidStake); From 457aed935cc6f93f6e8dd154c6b4beaddb56ef2e Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Tue, 3 Sep 2024 11:39:38 -0700 Subject: [PATCH 03/10] refactor: no spreading in state obj --- .../src/exos/cosmos-orchestration-account.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/orchestration/src/exos/cosmos-orchestration-account.js b/packages/orchestration/src/exos/cosmos-orchestration-account.js index 576532eaf8b..66087ad86fa 100644 --- a/packages/orchestration/src/exos/cosmos-orchestration-account.js +++ b/packages/orchestration/src/exos/cosmos-orchestration-account.js @@ -209,7 +209,7 @@ export const prepareCosmosOrchestrationAccountKit = ( * @returns {State} */ ({ chainAddress, bondDenom, localAddress, remoteAddress }, io) => { - const { storageNode, ...rest } = io; + const { storageNode } = io; // must be the fully synchronous maker because the kit is held in durable state const topicKit = makeRecorderKit(storageNode, PUBLIC_TOPICS.account[1]); // TODO determine what goes in vstorage https://github.com/Agoric/agoric-sdk/issues/9066 @@ -223,13 +223,16 @@ export const prepareCosmosOrchestrationAccountKit = ( }), ); + const { account, icqConnection, timer } = io; return { - chainAddress, + account, bondDenom, + chainAddress, + icqConnection, localAddress, remoteAddress, + timer, topicKit, - ...rest, }; }, { From ce2755d6bfd131ae48b74bec6334718d7ba4e9d3 Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Fri, 9 Aug 2024 10:33:00 -0400 Subject: [PATCH 04/10] refactor: makeSharedStateRecord --- packages/async-flow/index.js | 2 +- packages/async-flow/src/endowments.js | 2 +- packages/orchestration/src/examples/sendAnywhere.contract.js | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/async-flow/index.js b/packages/async-flow/index.js index 7c6e499a9c0..9ea1e5a6f19 100644 --- a/packages/async-flow/index.js +++ b/packages/async-flow/index.js @@ -1,3 +1,3 @@ export * from './src/async-flow.js'; export * from './src/types.js'; -export { makeStateRecord } from './src/endowments.js'; +export { makeSharedStateRecord } from './src/endowments.js'; diff --git a/packages/async-flow/src/endowments.js b/packages/async-flow/src/endowments.js index 209eae0c8f2..1aba97efaaf 100644 --- a/packages/async-flow/src/endowments.js +++ b/packages/async-flow/src/endowments.js @@ -64,7 +64,7 @@ export const forwardingMethods = rem => { * @param {R} dataRecord * @returns {R} */ -export const makeStateRecord = dataRecord => +export const makeSharedStateRecord = dataRecord => harden( create( objectPrototype, diff --git a/packages/orchestration/src/examples/sendAnywhere.contract.js b/packages/orchestration/src/examples/sendAnywhere.contract.js index f7dc456b135..11a79bb6498 100644 --- a/packages/orchestration/src/examples/sendAnywhere.contract.js +++ b/packages/orchestration/src/examples/sendAnywhere.contract.js @@ -1,4 +1,4 @@ -import { makeStateRecord } from '@agoric/async-flow'; +import { makeSharedStateRecord } from '@agoric/async-flow'; import { AmountShape } from '@agoric/ertp'; import { InvitationShape } from '@agoric/zoe/src/typeGuards.js'; import { M } from '@endo/patterns'; @@ -51,7 +51,7 @@ const contract = async ( zone, { chainHub, orchestrateAll, zoeTools }, ) => { - const contractState = makeStateRecord( + const contractState = makeSharedStateRecord( /** @type {{ account: OrchestrationAccount | undefined }} */ { localAccount: undefined, }, From 6ed057e1ba1cdc42eb642fdb49adc46f29b2fd6f Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Tue, 3 Sep 2024 12:55:30 -0700 Subject: [PATCH 05/10] docs: TODOs in unbond example --- .../orchestration/src/examples/unbond.flows.js | 14 +++++++------- .../snapshots/unbond.contract.test.ts.md | 2 +- .../snapshots/unbond.contract.test.ts.snap | Bin 1000 -> 991 bytes 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/orchestration/src/examples/unbond.flows.js b/packages/orchestration/src/examples/unbond.flows.js index 307cdeccea5..c936d93b630 100644 --- a/packages/orchestration/src/examples/unbond.flows.js +++ b/packages/orchestration/src/examples/unbond.flows.js @@ -24,13 +24,12 @@ export const unbondAndLiquidStake = async ( _offerArgs, ) => { console.log('zcf within the membrane', zcf); - // We would actually alreaady have the account from the orchestrator - // ??? could these be passed in? It would reduce the size of this handler, - // keeping it focused on long-running operations. - const omni = await orch.getChain('omniflixhub'); - const omniAccount = await omni.makeAccount(); + // Osmosis is one of the few chains with icqEnabled + const osmosis = await orch.getChain('osmosis'); + // In a real world scenario, accounts would be re-used across invokations of the handler + const osmoAccount = await osmosis.makeAccount(); - // TODO implement these + // TODO https://github.com/Agoric/agoric-sdk/issues/10016 // const delegations = await celestiaAccount.getDelegations(); // // wait for the undelegations to be complete (may take weeks) // await celestiaAccount.undelegate(delegations); @@ -41,7 +40,8 @@ export const unbondAndLiquidStake = async ( // TODO the `TIA` string actually needs to be the Brand from AgoricNames // const tiaAmt = await celestiaAccount.getBalance('TIA'); // await celestiaAccount.transfer(tiaAmt, strideAccount.getAddress()); + // TODO https://github.com/Agoric/agoric-sdk/issues/10017 // await strideAccount.liquidStake(tiaAmt); - console.log(omniAccount, strideAccount); + console.log(osmoAccount, strideAccount); }; harden(unbondAndLiquidStake); diff --git a/packages/orchestration/test/examples/snapshots/unbond.contract.test.ts.md b/packages/orchestration/test/examples/snapshots/unbond.contract.test.ts.md index 7cfd1ffe8da..dc4bf4aa5a3 100644 --- a/packages/orchestration/test/examples/snapshots/unbond.contract.test.ts.md +++ b/packages/orchestration/test/examples/snapshots/unbond.contract.test.ts.md @@ -40,7 +40,7 @@ Generated by [AVA](https://avajs.dev). Orchestrator_kindHandle: 'Alleged: kind', RemoteChainFacade_kindHandle: 'Alleged: kind', chainName: { - omniflixhub: 'Alleged: RemoteChainFacade public', + osmosis: 'Alleged: RemoteChainFacade public', stride: 'Alleged: RemoteChainFacade public', }, ibcTools: { diff --git a/packages/orchestration/test/examples/snapshots/unbond.contract.test.ts.snap b/packages/orchestration/test/examples/snapshots/unbond.contract.test.ts.snap index 34c192aeb28758f92e409468354cc514965f4311..26978e4b049fdfe5a9797712e619755a2f1735e8 100644 GIT binary patch literal 991 zcmV<510eiCRzVRy~g!MHqf&cWv+ba$mYT!X=mB6o5d2h6d3f=Yn-nE?jh% zTm(Xj#F}K1>&J#do(5B?I8);LD zq*J>l?47Pq@g7yNd2HI&&5`8kIyYr{G`v4&cqRR%}i6b?SqgCtvhH@E74uK6&4XO2DK6A$>>1OD`Y z3qEki2fp`#zkT4<0Js(aDgYh^PC&icfQBUaB>;X4fM)^lM#-U=q%M`hsmGVo6ss8$?_sIcSf72vH3AS=LkE`8ad&!Q`ZdRTIDxm}NYLeh zcFYr@+LBwX#)PvI>sh9wvw2wzy%bt4>cLJURhi)}H88Z_pQ|co6wVhF3dcA%5h#$| z=GfQmBdZNx)Q#;UOD$jI6f1;0txPL^bJSa@Vjs{pH@9h*n^9l*;gq#LVKu0bso^8a zr97o2^`N26L28-q@#8|aqBQgJI_-~FwkEC(IPG4i{VW7o#rc{3`{9fGc}DxC{%|m{ z24#HV!L*gPE#z=>Vw$66E{2ZI+q*jH>7;g$_p?@KOsC4$_OzUD_RVO%xvv?OFOWRN z5I&#j?RKb84a#V|aJ`+Kx}q08&CVh3Y0DSeeua=$t_&hJwEmFxcm_SpL7`}}n_A0J zZT^@;mim-+xjg|#2@k zUaLCvrGf*d3fyyPX9^b*$x{47H8;mn)zm7kt9W1BO+|cUX?HYAJSW(dtW8BS{(or_ z-a>hv^t>ZbPOK3zU&Q`7CHKnJ%eyxXRY{ARBd%g@PSek#$qspvN_*PMEeI4Q;P%Ac z<&IO~w9>TK<~vbHyg#bI6F2SQ^U^;10HAX}LAaz%F7NG(-;1>-ZMSKg*J#UfQ_Eh} N{{dGF4^C_g000tN<{AJ1 literal 1000 zcmV3RzVR!xuFL>PX?b`m?={nGAIw%tOCgb&iggLm{zF~f~uEHP9Yo=7@Ky2>OE9>p;=^!=rmT+}f7h)?_$fPDa; z0{9ug9{~OZaDf1C5MYB;*Gcst{D?({km>K*+8W3*NDX8a8^qZpE=Xmayzow{X((9Z zdKwCzbQ@Q>x!vP3-lH-WY(iUZ3c)%oeycIUERBsr7AYMwy@25gA$tUvzyVF}%cv#P zFoQnNL4Ks9(KIp{@?#EJRyyjk#As@`k{KL0GuXSUhDo;iW^UE}Sg}z6&m23z2M+Ls z1N`m)7hK@B3w-MWf4acS9&ptIqz63otbjW60riRZlL!3b0Z%;O)rv*0+Vr4T0q#_Q zA1c6B6*#T}pH+chtH3{1pkA{ig2Ilk)PUD&fT#iA*z{$KK98;x>S4*s<#sdfbJ?6^ zrEDoYZ2dC8S5>%mSdm6_p9In;D8Sg0!J6wVhF3dcA% z6)2GH=Ga%ABcn7c>&Etxp@x+?#R?&hYqN^q9QRf#qjzbC>07kN^tdlvf5uv$vRagj zRI@SVVxH0#YS>cxAT^Qdv*SXxf;98;8XZhlwx+HP8SP!8gDeDD#kraO+y1lrc~1Me z!Dui=EgG@XgJ~;oTgc(&)HFxST=XrS*LPLYS4ra_?`NYKMJko1v8Tjhvu{rG(!PqQ zc!uODhVc1JZ?{XiY|)6urR(kN%oSDoG&_f^uM8`<{R$!NTp0w>$of6nXBqS`2l+gV zZYU+jwfTJxSsKu&$IJ;hF0c7Ex~^59C+rqAQMY`W&K;}a6=|53R3xWJ%T~7UO~Xa9 zfa%G&t~9l#_lZ0CTU}i(jB@XP2Yto9HJryj@Wi)ni<(VyHv(Wg06q*XYQ1m|eHj2> z2f*I}aH($5mkJKttplIfv@?YZiDW7MuAZA?sSK5jn=;<#cTyf78QL3<62}VmlIly| z7W`f}4JT_Cry(ws=tuUy`}p{Y#TOdl~BGkuz#=3#Wml2n+} zR&PO|Fa@`#_BMBt4y&DJ{kGVNeB%682mZ2C?>{Z|^N#?!@H2!78ZvQjXYyujgtXJ4 W9oC?2!*nBiS^ozBnc<{x3jhHBD&ac- From 32970da83eb9a88c5a49bd43d80e7fd663d1608a Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Tue, 3 Sep 2024 14:37:04 -0700 Subject: [PATCH 06/10] chore: rename Proxying inv maker --- packages/boot/test/bootstrapTests/orchestration.test.ts | 8 ++++---- packages/orchestration/src/examples/basic-flows.flows.js | 4 ++-- packages/orchestration/src/exos/portfolio-holder-kit.js | 4 ++-- .../orchestration/test/exos/portfolio-holder-kit.test.ts | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/boot/test/bootstrapTests/orchestration.test.ts b/packages/boot/test/bootstrapTests/orchestration.test.ts index c619b911af4..a6628c1d775 100644 --- a/packages/boot/test/bootstrapTests/orchestration.test.ts +++ b/packages/boot/test/bootstrapTests/orchestration.test.ts @@ -532,7 +532,7 @@ test.serial('basic-flows - portfolio holder', async t => { invitationSpec: { source: 'continuing', previousOffer: 'request-portfolio-acct', - invitationMakerName: 'MakeInvitation', + invitationMakerName: 'Proxying', invitationArgs: [ 'cosmoshub', 'Delegate', @@ -550,7 +550,7 @@ test.serial('basic-flows - portfolio holder', async t => { invitationSpec: { source: 'continuing', previousOffer: 'request-portfolio-acct', - invitationMakerName: 'MakeInvitation', + invitationMakerName: 'Proxying', invitationArgs: [ 'agoric', 'Delegate', @@ -570,7 +570,7 @@ test.serial('basic-flows - portfolio holder', async t => { invitationSpec: { source: 'continuing', previousOffer: 'request-portfolio-acct', - invitationMakerName: 'MakeInvitation', + invitationMakerName: 'Proxying', invitationArgs: [ 'cosmoshub', 'Delegate', @@ -590,7 +590,7 @@ test.serial('basic-flows - portfolio holder', async t => { invitationSpec: { source: 'continuing', previousOffer: 'request-portfolio-acct', - invitationMakerName: 'MakeInvitation', + invitationMakerName: 'Proxying', invitationArgs: [ 'agoric', 'Delegate', diff --git a/packages/orchestration/src/examples/basic-flows.flows.js b/packages/orchestration/src/examples/basic-flows.flows.js index 2948d3c6c49..0d01c4a7696 100644 --- a/packages/orchestration/src/examples/basic-flows.flows.js +++ b/packages/orchestration/src/examples/basic-flows.flows.js @@ -37,8 +37,8 @@ harden(makeOrchAccount); /** * Create accounts on multiple chains and return them in a single continuing * offer with invitations makers for Delegate, WithdrawRewards, Transfer, etc. - * Calls to the underlying invitationMakers are proxied through the - * `MakeInvitation` invitation maker. + * Calls to the underlying invitationMakers are proxied through the `Proxying` + * invitation maker. * * @satisfies {OrchestrationFlow} * @param {Orchestrator} orch diff --git a/packages/orchestration/src/exos/portfolio-holder-kit.js b/packages/orchestration/src/exos/portfolio-holder-kit.js index 176b61ce916..059eebe03e1 100644 --- a/packages/orchestration/src/exos/portfolio-holder-kit.js +++ b/packages/orchestration/src/exos/portfolio-holder-kit.js @@ -43,7 +43,7 @@ const preparePortfolioHolderKit = (zone, { asVow, when }) => { 'PortfolioHolderKit', { invitationMakers: M.interface('InvitationMakers', { - MakeInvitation: M.call( + Proxying: M.call( ChainNameShape, M.string(), M.arrayOf(M.any()), @@ -95,7 +95,7 @@ const preparePortfolioHolderKit = (zone, { asVow, when }) => { * @param {IA} invitationArgs * @returns {Promise>} */ - MakeInvitation(chainName, action, invitationArgs) { + Proxying(chainName, action, invitationArgs) { const { accounts } = this.state; accounts.has(chainName) || Fail`no account found for ${chainName}`; const account = accounts.get(chainName); diff --git a/packages/orchestration/test/exos/portfolio-holder-kit.test.ts b/packages/orchestration/test/exos/portfolio-holder-kit.test.ts index 5151873f908..62f48203f25 100644 --- a/packages/orchestration/test/exos/portfolio-holder-kit.test.ts +++ b/packages/orchestration/test/exos/portfolio-holder-kit.test.ts @@ -78,7 +78,7 @@ test('portfolio holder kit behaviors', async t => { const { invitationMakers } = await E(holder).asContinuingOffer(); - const delegateInv = await E(invitationMakers).MakeInvitation( + const delegateInv = await E(invitationMakers).Proxying( 'cosmoshub', 'Delegate', [ @@ -99,7 +99,7 @@ test('portfolio holder kit behaviors', async t => { // note: mocked zcf (we are not in a contract) returns inv description // @ts-expect-error Argument of type 'string' is not assignable to parameter of type 'Vow' 'Delegate', - 'any invitation maker accessible via MakeInvitation', + 'any invitation maker accessible via Proxying', ); const osmosisAccount = await makeCosmosAccount({ From 868408fed475aa8963014aa9bd7da7c63766d681 Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Wed, 4 Sep 2024 08:46:35 -0700 Subject: [PATCH 07/10] test: rename facade-durability module --- .../test/{facade.test.ts => facade-durability.test.ts} | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) rename packages/orchestration/test/{facade.test.ts => facade-durability.test.ts} (100%) diff --git a/packages/orchestration/test/facade.test.ts b/packages/orchestration/test/facade-durability.test.ts similarity index 100% rename from packages/orchestration/test/facade.test.ts rename to packages/orchestration/test/facade-durability.test.ts index 99c685433a3..ffc4d06f20d 100644 --- a/packages/orchestration/test/facade.test.ts +++ b/packages/orchestration/test/facade-durability.test.ts @@ -1,15 +1,15 @@ import { test as anyTest } from '@agoric/zoe/tools/prepare-test-env-ava.js'; +import { makeIssuerKit } from '@agoric/ertp'; +import { reincarnate } from '@agoric/swingset-liveslots/tools/setup-vat-data.js'; import { prepareSwingsetVowTools } from '@agoric/vow/vat.js'; import { setupZCFTest } from '@agoric/zoe/test/unitTests/zcf/setupZcfTest.js'; -import { reincarnate } from '@agoric/swingset-liveslots/tools/setup-vat-data.js'; -import { makeIssuerKit } from '@agoric/ertp'; import type { CosmosChainInfo, IBCConnectionInfo } from '../src/cosmos-api.js'; +import fetchedChainInfo from '../src/fetched-chain-info.js'; // Refresh with scripts/refresh-chain-info.ts import type { Chain } from '../src/orchestration-api.js'; +import { denomHash } from '../src/utils/denomHash.js'; import { provideOrchestration } from '../src/utils/start-helper.js'; import { commonSetup, provideDurableZone } from './supports.js'; -import { denomHash } from '../src/utils/denomHash.js'; -import fetchedChainInfo from '../src/fetched-chain-info.js'; // Refresh with scripts/refresh-chain-info.ts const test = anyTest; From 785866a11dc9c0ad9a3e011f465e134ca8f219a3 Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Wed, 4 Sep 2024 08:49:29 -0700 Subject: [PATCH 08/10] feat: flows in orchestrateAll context --- packages/orchestration/src/facade.js | 35 ++++++++-- packages/orchestration/test/facade.test.ts | 81 ++++++++++++++++++++++ 2 files changed, 111 insertions(+), 5 deletions(-) create mode 100644 packages/orchestration/test/facade.test.ts diff --git a/packages/orchestration/src/facade.js b/packages/orchestration/src/facade.js index 6be7807c248..63c34400dbd 100644 --- a/packages/orchestration/src/facade.js +++ b/packages/orchestration/src/facade.js @@ -1,5 +1,4 @@ /** @file Orchestration facade */ - import { assertAllDefined } from '@agoric/internal'; /** @@ -95,9 +94,12 @@ export const makeOrchestrationFacade = ({ /** * Orchestrate all the guest functions. * + * If the `guestFns` object is provided as a property of `hostCtx` the + * functions will be available within the other guests. + * * NOTE multiple calls to this with the same guestFn name will fail * - * @template HC - host context + * @template {Record} HC - host context * @template {{ * [durableName: string]: OrchestrationFlow>; * }} GFM @@ -106,16 +108,39 @@ export const makeOrchestrationFacade = ({ * @param {HC} hostCtx * @returns {{ [N in keyof GFM]: HostForGuest }} */ - const orchestrateAll = (guestFns, hostCtx) => - /** @type {{ [N in keyof GFM]: HostForGuest }} */ ( + const orchestrateAll = (guestFns, hostCtx) => { + const getMappedFlows = () => { + return Object.fromEntries( + Object.keys(guestFns).map(name => [ + name, + // eslint-disable-next-line no-use-before-define + (...args) => orcFns[name](...args), + ]), + ); + }; + + const mappedContext = Object.fromEntries( + Object.entries(hostCtx).map(([key, value]) => [ + key, + // TODO: support matching individual guest functions anywhere in the context + // instead of matching the record as a whole + // https://github.com/Agoric/agoric-sdk/issues/9823 + value === guestFns ? getMappedFlows() : value, + ]), + ); + + const orcFns = /** @type {{ [N in keyof GFM]: HostForGuest }} */ ( Object.fromEntries( Object.entries(guestFns).map(([name, guestFn]) => [ name, - orchestrate(name, hostCtx, guestFn), + orchestrate(name, mappedContext, guestFn), ]), ) ); + return { ...orcFns }; + }; + return harden({ orchestrate, orchestrateAll, diff --git a/packages/orchestration/test/facade.test.ts b/packages/orchestration/test/facade.test.ts new file mode 100644 index 00000000000..07a214cd4c8 --- /dev/null +++ b/packages/orchestration/test/facade.test.ts @@ -0,0 +1,81 @@ +/* eslint-disable @jessie.js/safe-await-separator */ +import { test as anyTest } from '@agoric/zoe/tools/prepare-test-env-ava.js'; + +import type { VowTools } from '@agoric/vow'; +import { prepareSwingsetVowTools } from '@agoric/vow/vat.js'; +import { setupZCFTest } from '@agoric/zoe/test/unitTests/zcf/setupZcfTest.js'; +import { makeHeapZone } from '@agoric/zone'; +import type { TestFn } from 'ava'; +import type { OrchestrationFlow } from '../src/orchestration-api.js'; +import { provideOrchestration } from '../src/utils/start-helper.js'; +import { commonSetup } from './supports.js'; + +const test = anyTest as TestFn<{ vt: VowTools; orchestrateAll: any; zcf: ZCF }>; + +test.beforeEach(async t => { + const { facadeServices, commonPrivateArgs } = await commonSetup(t); + const { zcf } = await setupZCFTest(); + const zone = makeHeapZone(); + const vt = prepareSwingsetVowTools(zone); + + const orchKit = provideOrchestration( + zcf, + zone.mapStore('test'), + { + agoricNames: facadeServices.agoricNames, + timerService: facadeServices.timerService, + storageNode: commonPrivateArgs.storageNode, + orchestrationService: facadeServices.orchestrationService, + localchain: facadeServices.localchain, + }, + commonPrivateArgs.marshaller, + ); + + const { orchestrateAll } = orchKit; + t.context = { vt, orchestrateAll, zcf }; +}); + +test('calls between flows', async t => { + const { vt, orchestrateAll, zcf } = t.context; + + const flows = { + outer(orch, ctx, ...recipients) { + return ctx.peerFlows.inner('Hello', ...recipients); + }, + inner(orch, ctx, ...strs) { + return Promise.resolve(strs.join(' ')); + }, + } as Record>; + + const { outer, outer2, inner } = orchestrateAll(flows, { + peerFlows: flows, + zcf, + }); + + t.deepEqual(await vt.when(inner('a', 'b', 'c')), 'a b c'); + t.deepEqual(await vt.when(outer('a', 'b', 'c')), 'Hello a b c'); +}); + +// UNTIL https://github.com/Agoric/agoric-sdk/issues/9823 +test('context mapping limits', async t => { + const { vt, orchestrateAll, zcf } = t.context; + + const flows = { + outer(orch, ctx, ...recipients) { + return ctx.peerFlows.inner('Hello', ...recipients); + }, + inner(orch, ctx, ...strs) { + return Promise.resolve(strs.join(' ')); + }, + } as Record>; + + const { outer } = orchestrateAll(flows, { + peerFlows: { ...flows }, + zcf, + }); + + // `peerFlows` did not have the same identity as `guestFns` + await t.throwsAsync(vt.when(outer('a', 'b', 'c')), { + message: 'converting apply result: vow expected "[Promise]"', + }); +}); From 72905fa7be54d1cdb906a019408932e83a9af17e Mon Sep 17 00:00:00 2001 From: Mathieu Hofman Date: Thu, 5 Sep 2024 05:10:23 +0000 Subject: [PATCH 09/10] feat(internal): deepMapObject --- packages/internal/src/utils.js | 49 +++++++++++++++ packages/internal/test/utils.test.js | 93 ++++++++++++++++++++++++++++ 2 files changed, 142 insertions(+) diff --git a/packages/internal/src/utils.js b/packages/internal/src/utils.js index ed82db385b6..adde6849d6e 100644 --- a/packages/internal/src/utils.js +++ b/packages/internal/src/utils.js @@ -52,6 +52,55 @@ export const deeplyFulfilledObject = async obj => { return deeplyFulfilled(obj); }; +/** + * @param {any} value + * @param {string | undefined} name + * @param {object | undefined} container + * @param {(value: any, name: string, record: object) => any} mapper + * @returns {any} + */ +const deepMapObjectInternal = (value, name, container, mapper) => { + if (container && typeof name === 'string') { + const mapped = mapper(value, name, container); + if (mapped !== value) { + return mapped; + } + } + + if (typeof value !== 'object' || !value) { + return value; + } + + let wasMapped = false; + const mappedEntries = Object.entries(value).map(([innerName, innerValue]) => { + const mappedInnerValue = deepMapObjectInternal( + innerValue, + innerName, + value, + mapper, + ); + wasMapped ||= mappedInnerValue !== innerValue; + return [innerName, mappedInnerValue]; + }); + + return wasMapped ? Object.fromEntries(mappedEntries) : value; +}; + +/** + * Traverses a record object structure deeply, calling a replacer for each + * enumerable string property values of an object. If none of the values are + * changed, the original object is used as-is, maintaining its identity. + * + * When an object is found as a property value, the replacer is first called on + * it. If not replaced, the object is then traversed. + * + * @param {object} obj + * @param {(value: any, name: string, record: object) => any} mapper + * @returns {object} + */ +export const deepMapObject = (obj, mapper) => + deepMapObjectInternal(obj, undefined, undefined, mapper); + /** * Returns a function that uses a millisecond-based time-since-epoch capability * (such as `performance.now`) to measure execution time of an async function diff --git a/packages/internal/test/utils.test.js b/packages/internal/test/utils.test.js index 27d391ca488..7d1b9ba5092 100644 --- a/packages/internal/test/utils.test.js +++ b/packages/internal/test/utils.test.js @@ -9,6 +9,7 @@ import { untilTrue, forever, deeplyFulfilledObject, + deepMapObject, synchronizedTee, } from '../src/utils.js'; @@ -30,6 +31,98 @@ test('deeplyFulfilledObject', async t => { }); }); +/** + * @typedef {object} DeepMapObjectTestParams + * @property {any} input + * @property {[any, any][]} replacements + * @property {string[][]} unchangedPaths + * @property {any} [expectedOutput] + */ + +/** @type {import('ava').Macro<[DeepMapObjectTestParams]>} */ +const deepMapObjectTest = test.macro({ + title(providedTitle, { input }) { + return `deepMapObject - ${providedTitle || JSON.stringify(input)}`; + }, + exec(t, { input, replacements, unchangedPaths, expectedOutput }) { + const replacementMap = new Map(replacements); + const output = deepMapObject(input, val => + replacementMap.has(val) ? replacementMap.get(val) : val, + ); + + for (const unchangedPath of unchangedPaths) { + /** @type {any} */ + let inputVal = input; + /** @type {any} */ + let outputVal = output; + for (const pathPart of unchangedPath) { + inputVal = inputVal[pathPart]; + outputVal = outputVal[pathPart]; + } + t.is( + outputVal, + inputVal, + `${['obj', ...unchangedPath].join('.')} is unchanged`, + ); + } + + if (expectedOutput) { + t.deepEqual(output, expectedOutput); + } + }, +}); + +test('identity', deepMapObjectTest, { + input: { foo: 42 }, + replacements: [], + unchangedPaths: [[]], +}); +test('non object', deepMapObjectTest, { + input: 'not an object', + replacements: [['not an object', 'not replaced']], + unchangedPaths: [[]], + expectedOutput: 'not an object', +}); +test('one level deep', deepMapObjectTest, { + input: { replace: 'replace me', notChanged: {} }, + replacements: [['replace me', 'replaced']], + unchangedPaths: [['notChanged']], + expectedOutput: { replace: 'replaced', notChanged: {} }, +}); + +const testRecord = { maybeReplace: 'replace me' }; +test('replace first before deep map', deepMapObjectTest, { + input: { replace: testRecord, notChanged: {} }, + replacements: [ + [testRecord, { different: 'something new' }], + ['replace me', 'should not be replaced'], + ], + unchangedPaths: [['notChanged']], + expectedOutput: { replace: { different: 'something new' }, notChanged: {} }, +}); + +test('not mapping top level container', deepMapObjectTest, { + input: testRecord, + replacements: [ + [testRecord, { different: 'should not be different' }], + ['replace me', 'replaced'], + ], + unchangedPaths: [], + expectedOutput: { maybeReplace: 'replaced' }, +}); +test('deep mapping', deepMapObjectTest, { + input: { + one: { two: { three: 'replace me' }, notChanged: {} }, + another: 'replace me', + }, + replacements: [['replace me', 'replaced']], + unchangedPaths: [['one', 'notChanged']], + expectedOutput: { + one: { two: { three: 'replaced' }, notChanged: {} }, + another: 'replaced', + }, +}); + test('makeMeasureSeconds', async t => { const times = [1000.25, 2000.75, NaN]; /** @type {() => number} */ From 84d3ea6c3afad8d81508a1164e12f94be7f1fdd8 Mon Sep 17 00:00:00 2001 From: Mathieu Hofman Date: Thu, 5 Sep 2024 05:11:15 +0000 Subject: [PATCH 10/10] feat(orchestration): map flows anywhere in context --- packages/orchestration/src/facade.js | 29 ++++++++-------------- packages/orchestration/test/facade.test.ts | 10 +++----- 2 files changed, 14 insertions(+), 25 deletions(-) diff --git a/packages/orchestration/src/facade.js b/packages/orchestration/src/facade.js index 63c34400dbd..71a11ad430e 100644 --- a/packages/orchestration/src/facade.js +++ b/packages/orchestration/src/facade.js @@ -1,5 +1,5 @@ /** @file Orchestration facade */ -import { assertAllDefined } from '@agoric/internal'; +import { assertAllDefined, deepMapObject } from '@agoric/internal'; /** * @import {AsyncFlowTools, GuestInterface, HostArgs, HostOf} from '@agoric/async-flow'; @@ -109,26 +109,19 @@ export const makeOrchestrationFacade = ({ * @returns {{ [N in keyof GFM]: HostForGuest }} */ const orchestrateAll = (guestFns, hostCtx) => { - const getMappedFlows = () => { - return Object.fromEntries( - Object.keys(guestFns).map(name => [ - name, - // eslint-disable-next-line no-use-before-define - (...args) => orcFns[name](...args), - ]), - ); - }; - - const mappedContext = Object.fromEntries( - Object.entries(hostCtx).map(([key, value]) => [ - key, - // TODO: support matching individual guest functions anywhere in the context - // instead of matching the record as a whole - // https://github.com/Agoric/agoric-sdk/issues/9823 - value === guestFns ? getMappedFlows() : value, + const mappedFlows = new Map( + Object.entries(guestFns).map(([name, guestFn]) => [ + guestFn, + // eslint-disable-next-line no-use-before-define + (...args) => orcFns[name](...args), ]), ); + const mappedContext = deepMapObject( + hostCtx, + val => mappedFlows.get(val) || val, + ); + const orcFns = /** @type {{ [N in keyof GFM]: HostForGuest }} */ ( Object.fromEntries( Object.entries(guestFns).map(([name, guestFn]) => [ diff --git a/packages/orchestration/test/facade.test.ts b/packages/orchestration/test/facade.test.ts index 07a214cd4c8..8ee3e4aaa45 100644 --- a/packages/orchestration/test/facade.test.ts +++ b/packages/orchestration/test/facade.test.ts @@ -56,8 +56,7 @@ test('calls between flows', async t => { t.deepEqual(await vt.when(outer('a', 'b', 'c')), 'Hello a b c'); }); -// UNTIL https://github.com/Agoric/agoric-sdk/issues/9823 -test('context mapping limits', async t => { +test('context mapping individual flows', async t => { const { vt, orchestrateAll, zcf } = t.context; const flows = { @@ -70,12 +69,9 @@ test('context mapping limits', async t => { } as Record>; const { outer } = orchestrateAll(flows, { - peerFlows: { ...flows }, + peerFlows: { inner: flows.inner }, zcf, }); - // `peerFlows` did not have the same identity as `guestFns` - await t.throwsAsync(vt.when(outer('a', 'b', 'c')), { - message: 'converting apply result: vow expected "[Promise]"', - }); + t.deepEqual(await vt.when(outer('a', 'b', 'c')), 'Hello a b c'); });