diff --git a/packages/orchestration/src/chain-info.js b/packages/orchestration/src/chain-info.js index 90ed61d885f..f387faca34e 100644 --- a/packages/orchestration/src/chain-info.js +++ b/packages/orchestration/src/chain-info.js @@ -10,7 +10,7 @@ import fetchedChainInfo from './fetched-chain-info.js'; const knownChains = /** @satisfies {Record} */ ( harden({ ...fetchedChainInfo, - // XXX does not have useful connections + // FIXME does not have useful connections // UNTIL https://github.com/Agoric/agoric-sdk/issues/9492 agoriclocal: { chainId: 'agoriclocal', diff --git a/packages/orchestration/src/examples/sendAnywhere.contract.js b/packages/orchestration/src/examples/sendAnywhere.contract.js index 6ea16f3c44c..5319e1fe0f9 100644 --- a/packages/orchestration/src/examples/sendAnywhere.contract.js +++ b/packages/orchestration/src/examples/sendAnywhere.contract.js @@ -52,6 +52,7 @@ export const start = async (zcf, privateArgs, baggage) => { privateArgs.marshaller, ); + /** @type {import('../orchestration-api.js').OrchestrationAccount} */ let contractAccount; const findBrandInVBank = async brand => { @@ -79,7 +80,7 @@ export const start = async (zcf, privateArgs, baggage) => { const { denom } = await findBrandInVBank(amt.brand); const chain = await orch.getChain(chainName); - // XXX ok to use a heap var crossing the membrane scope this way? + // FIXME ok to use a heap var crossing the membrane scope this way? if (!contractAccount) { const agoricChain = await orch.getChain('agoric'); contractAccount = await agoricChain.makeAccount(); @@ -88,7 +89,7 @@ export const start = async (zcf, privateArgs, baggage) => { const info = await chain.getChainInfo(); const { chainId } = info; const { [kw]: pmtP } = await withdrawFromSeat(zcf, seat, give); - await E.when(pmtP, pmt => contractAccount.deposit(pmt, amt)); + await E.when(pmtP, pmt => contractAccount.deposit(pmt)); await contractAccount.transfer( { denom, value: amt.value }, { diff --git a/packages/orchestration/src/examples/stakeBld.contract.js b/packages/orchestration/src/examples/stakeBld.contract.js index 17fddccf346..76f2f411540 100644 --- a/packages/orchestration/src/examples/stakeBld.contract.js +++ b/packages/orchestration/src/examples/stakeBld.contract.js @@ -10,7 +10,7 @@ import { V } from '@agoric/vow/vat.js'; import { E } from '@endo/far'; import { deeplyFulfilled } from '@endo/marshal'; import { M } from '@endo/patterns'; -import { prepareLocalChainAccountKit } from '../exos/local-chain-account-kit.js'; +import { prepareLocalOrchestrationAccountKit } from '../exos/local-orchestration-account.js'; import { makeChainHub } from '../utils/chainHub.js'; /** @@ -41,7 +41,7 @@ export const start = async (zcf, privateArgs, baggage) => { privateArgs.marshaller, ); - const makeLocalChainAccountKit = prepareLocalChainAccountKit( + const makeLocalOrchestrationAccountKit = prepareLocalOrchestrationAccountKit( zone, makeRecorderKit, zcf, @@ -58,10 +58,14 @@ export const start = async (zcf, privateArgs, baggage) => { async function makeLocalAccountKit() { const account = await V(privateArgs.localchain).makeAccount(); const address = await V(account).getAddress(); - // XXX 'address' is implied by 'account'; use an async maker that get the value itself - return makeLocalChainAccountKit({ + // FIXME 'address' is implied by 'account'; use an async maker that get the value itself + return makeLocalOrchestrationAccountKit({ account, - address, + address: harden({ + address, + addressEncoding: 'bech32', + chainId: 'local', + }), storageNode: privateArgs.storageNode, }); } diff --git a/packages/orchestration/src/examples/stakeIca.contract.js b/packages/orchestration/src/examples/stakeIca.contract.js index 40fc24c08e1..e700f275468 100644 --- a/packages/orchestration/src/examples/stakeIca.contract.js +++ b/packages/orchestration/src/examples/stakeIca.contract.js @@ -10,7 +10,7 @@ import { import { InvitationShape } from '@agoric/zoe/src/typeGuards.js'; import { makeDurableZone } from '@agoric/zone/durable.js'; import { M } from '@endo/patterns'; -import { prepareCosmosOrchestrationAccount } from '../exos/cosmosOrchestrationAccount.js'; +import { prepareCosmosOrchestrationAccount } from '../exos/cosmos-orchestration-account.js'; const trace = makeTracer('StakeAtom'); /** diff --git a/packages/orchestration/src/examples/swapExample.contract.js b/packages/orchestration/src/examples/swapExample.contract.js index f50a70d1d87..cba2326800e 100644 --- a/packages/orchestration/src/examples/swapExample.contract.js +++ b/packages/orchestration/src/examples/swapExample.contract.js @@ -103,7 +103,12 @@ export const start = async (zcf, privateArgs, baggage) => { // deposit funds from user seat to LocalChainAccount const payments = await withdrawFromSeat(zcf, seat, give); - await deeplyFulfilled(objectMap(payments, localAccount.deposit)); + await deeplyFulfilled( + objectMap(payments, payment => + // @ts-expect-error payment is ERef which happens to work but isn't officially supported + localAccount.deposit(payment), + ), + ); seat.exit(); // build swap instructions with orcUtils library diff --git a/packages/orchestration/src/exos/chainAccountKit.js b/packages/orchestration/src/exos/chain-account-kit.js similarity index 98% rename from packages/orchestration/src/exos/chainAccountKit.js rename to packages/orchestration/src/exos/chain-account-kit.js index 187b661ea8e..1189055a5cb 100644 --- a/packages/orchestration/src/exos/chainAccountKit.js +++ b/packages/orchestration/src/exos/chain-account-kit.js @@ -131,7 +131,7 @@ export const prepareChainAccountKit = zone => }, /** Close the remote account */ async close() { - /// XXX what should the behavior be here? and `onClose`? + // FIXME what should the behavior be here? and `onClose`? // - retrieve assets? // - revoke the port? const { connection } = this.state; @@ -169,7 +169,7 @@ export const prepareChainAccountKit = zone => }, async onClose(_connection, reason) { trace(`ICA Channel closed. Reason: ${reason}`); - // XXX handle connection closing + // FIXME handle connection closing // XXX is there a scenario where a connection will unexpectedly close? _I think yes_ }, async onReceive(connection, bytes) { diff --git a/packages/orchestration/src/exos/cosmosOrchestrationAccount.js b/packages/orchestration/src/exos/cosmos-orchestration-account.js similarity index 100% rename from packages/orchestration/src/exos/cosmosOrchestrationAccount.js rename to packages/orchestration/src/exos/cosmos-orchestration-account.js diff --git a/packages/orchestration/src/exos/icqConnectionKit.js b/packages/orchestration/src/exos/icq-connection-kit.js similarity index 100% rename from packages/orchestration/src/exos/icqConnectionKit.js rename to packages/orchestration/src/exos/icq-connection-kit.js diff --git a/packages/orchestration/src/exos/local-chain-facade.js b/packages/orchestration/src/exos/local-chain-facade.js new file mode 100644 index 00000000000..870493c35c8 --- /dev/null +++ b/packages/orchestration/src/exos/local-chain-facade.js @@ -0,0 +1,67 @@ +/** @file ChainAccount exo */ +import { V } from '@agoric/vow/vat.js'; + +import { ChainFacadeI } from '../typeGuards.js'; + +/** + * @import {Zone} from '@agoric/base-zone'; + * @import {TimerService} from '@agoric/time'; + * @import {Remote} from '@agoric/internal'; + * @import {LocalChain} from '@agoric/vats/src/localchain.js'; + * @import {OrchestrationService} from '../service.js'; + * @import {MakeLocalOrchestrationAccountKit} from './local-orchestration-account.js'; + * @import {ChainInfo, CosmosChainInfo, IBCConnectionInfo, OrchestrationAccount} from '../types.js'; + */ + +/** + * @param {Zone} zone + * @param {{ + * makeLocalOrchestrationAccountKit: MakeLocalOrchestrationAccountKit; + * orchestration: Remote; + * storageNode: Remote; + * timer: Remote; + * localchain: Remote; + * }} powers + */ +export const prepareLocalChainFacade = ( + zone, + { makeLocalOrchestrationAccountKit, localchain, storageNode }, +) => + zone.exoClass( + 'LocalChainFacade', + ChainFacadeI, + /** + * @param {CosmosChainInfo} localChainInfo + */ + localChainInfo => { + return { localChainInfo }; + }, + { + async getChainInfo() { + return this.state.localChainInfo; + }, + + // FIXME parameterize on the remoteChainInfo to make() + // That used to work but got lost in the migration to Exo + /** @returns {Promise>} */ + async makeAccount() { + const { localChainInfo } = this.state; + const lcaP = V(localchain).makeAccount(); + const [lca, address] = await Promise.all([lcaP, V(lcaP).getAddress()]); + const { holder: account } = makeLocalOrchestrationAccountKit({ + account: lca, + address: harden({ + address, + chainId: localChainInfo.chainId, + addressEncoding: 'bech32', + }), + // FIXME storage path + storageNode, + }); + + return account; + }, + }, + ); +harden(prepareLocalChainFacade); +/** @typedef {ReturnType} MakeLocalChainFacade */ diff --git a/packages/orchestration/src/exos/local-chain-account-kit.js b/packages/orchestration/src/exos/local-orchestration-account.js similarity index 81% rename from packages/orchestration/src/exos/local-chain-account-kit.js rename to packages/orchestration/src/exos/local-orchestration-account.js index 14f2935dd1c..045c176a4c2 100644 --- a/packages/orchestration/src/exos/local-chain-account-kit.js +++ b/packages/orchestration/src/exos/local-orchestration-account.js @@ -1,24 +1,19 @@ /** @file Use-object for the owner of a localchain account */ -import { NonNullish } from '@agoric/assert'; import { typedJson } from '@agoric/cosmic-proto/vatsafe'; import { AmountShape, PaymentShape } from '@agoric/ertp'; import { makeTracer } from '@agoric/internal'; import { M } from '@agoric/vat-data'; +import { V } from '@agoric/vow/vat.js'; import { TopicsRecordShape } from '@agoric/zoe/src/contractSupport/index.js'; import { InvitationShape } from '@agoric/zoe/src/typeGuards.js'; -import { V } from '@agoric/vow/vat.js'; import { E } from '@endo/far'; -import { - AmountArgShape, - ChainAddressShape, - IBCTransferOptionsShape, -} from '../typeGuards.js'; import { maxClockSkew } from '../utils/cosmos.js'; +import { orchestrationAccountMethods } from '../utils/orchestrationAccount.js'; import { dateInSeconds, makeTimestampHelper } from '../utils/time.js'; /** * @import {LocalChainAccount} from '@agoric/vats/src/localchain.js'; - * @import {AmountArg, ChainAddress, DenomAmount, IBCMsgTransferOptions, CosmosChainInfo} from '@agoric/orchestration'; + * @import {AmountArg, ChainAddress, DenomAmount, IBCMsgTransferOptions, OrchestrationAccount, OrchestrationAccountI} from '@agoric/orchestration'; * @import {RecorderKit, MakeRecorderKit} from '@agoric/zoe/src/contractSupport/recorder.js'. * @import {Zone} from '@agoric/zone'; * @import {Remote} from '@agoric/internal'; @@ -26,7 +21,7 @@ import { dateInSeconds, makeTimestampHelper } from '../utils/time.js'; * @import {ChainHub} from '../utils/chainHub.js'; */ -const trace = makeTracer('LCAH'); +const trace = makeTracer('LOA'); const { Fail } = assert; /** @@ -38,20 +33,16 @@ const { Fail } = assert; * @typedef {{ * topicKit: RecorderKit; * account: LocalChainAccount; - * address: ChainAddress['address']; + * address: ChainAddress; * }} State */ const HolderI = M.interface('holder', { + ...orchestrationAccountMethods, getPublicTopics: M.call().returns(TopicsRecordShape), delegate: M.call(M.string(), AmountShape).returns(M.promise()), undelegate: M.call(M.string(), AmountShape).returns(M.promise()), - deposit: M.callWhen(PaymentShape).optional(AmountShape).returns(AmountShape), withdraw: M.callWhen(AmountShape).returns(PaymentShape), - transfer: M.call(AmountArgShape, ChainAddressShape) - .optional(IBCTransferOptionsShape) - .returns(M.promise()), - getAddress: M.call().returns(M.string()), executeTx: M.callWhen(M.arrayOf(M.record())).returns(M.arrayOf(M.record())), }); @@ -67,7 +58,7 @@ const PUBLIC_TOPICS = { * @param {Remote} timerService * @param {ChainHub} chainHub */ -export const prepareLocalChainAccountKit = ( +export const prepareLocalOrchestrationAccountKit = ( zone, makeRecorderKit, zcf, @@ -75,10 +66,10 @@ export const prepareLocalChainAccountKit = ( chainHub, ) => { const timestampHelper = makeTimestampHelper(timerService); - // TODO: rename to makeLocalOrchestrationAccount or the like to distinguish from lca + /** Make an object wrapping an LCA with Zoe interfaces. */ - const makeLocalChainAccountKit = zone.exoClassKit( - 'LCA Kit', + const makeLocalOrchestrationAccountKit = zone.exoClassKit( + 'Local Orchestration Account Kit', { holder: HolderI, invitationMakers: M.interface('invitationMakers', { @@ -92,8 +83,8 @@ export const prepareLocalChainAccountKit = ( /** * @param {object} initState * @param {LocalChainAccount} initState.account - * @param {ChainAddress['address']} initState.address - * @param {StorageNode} initState.storageNode + * @param {ChainAddress} initState.address + * @param {Remote} initState.storageNode * @returns {State} */ ({ account, address, storageNode }) => { @@ -101,7 +92,6 @@ export const prepareLocalChainAccountKit = ( // @ts-expect-error XXX Patterns const topicKit = makeRecorderKit(storageNode, PUBLIC_TOPICS.account[1]); - // #9162 use ChainAddress object instead of `address` string return { account, address, topicKit }; }, { @@ -138,6 +128,24 @@ export const prepareLocalChainAccountKit = ( }, }, holder: { + /** @type {OrchestrationAccount['getBalance']} */ + async getBalance(denomArg) { + // FIXME look up real values + // UNTIL https://github.com/Agoric/agoric-sdk/issues/9211 + const [brand, denom] = + typeof denomArg === 'string' + ? [/** @type {any} */ (null), denomArg] + : [denomArg, 'FIXME']; + + const natAmount = await V.when( + E(this.state.account).getBalance(brand), + ); + return harden({ denom, value: natAmount.value }); + }, + getBalances() { + throw new Error('not yet implemented'); + }, + getPublicTopics() { const { topicKit } = this.state; return harden({ @@ -207,9 +215,9 @@ export const prepareLocalChainAccountKit = ( * updater will get a special notification that the account is being * transferred. */ - /** @type {LocalChainAccount['deposit']} */ - async deposit(payment, optAmountShape) { - return V(this.state.account).deposit(payment, optAmountShape); + /** @type {OrchestrationAccount['deposit']} */ + async deposit(payment) { + await V(this.state.account).deposit(payment); }, /** @type {LocalChainAccount['withdraw']} */ async withdraw(amount) { @@ -220,9 +228,13 @@ export const prepareLocalChainAccountKit = ( // @ts-expect-error subtype return V(this.state.account).executeTx(messages); }, - /** @returns {ChainAddress['address']} */ + /** @returns {ChainAddress} */ getAddress() { - return NonNullish(this.state.address, 'Chain address not available.'); + return this.state.address; + }, + async send(toAccount, amount) { + // FIXME implement + console.log('send got', toAccount, amount); }, /** * @param {AmountArg} amount an ERTP {@link Amount} or a @@ -261,7 +273,7 @@ export const prepareLocalChainAccountKit = ( amount: String(amount.value), denom: amount.denom, }, - sender: this.state.address, + sender: this.state.address.address, receiver: destination.address, timeoutHeight: opts?.timeoutHeight ?? { revisionHeight: 0n, @@ -273,10 +285,15 @@ export const prepareLocalChainAccountKit = ( ]); trace('MsgTransfer result', result); }, + /** @type {OrchestrationAccount['transferSteps']} */ + transferSteps(amount, msg) { + console.log('transferSteps got', amount, msg); + return Promise.resolve(); + }, }, }, ); - return makeLocalChainAccountKit; + return makeLocalOrchestrationAccountKit; }; -/** @typedef {ReturnType} MakeLocalChainAccountKit */ -/** @typedef {ReturnType} LocalChainAccountKit */ +/** @typedef {ReturnType} MakeLocalOrchestrationAccountKit */ +/** @typedef {ReturnType} LocalOrchestrationAccountKit */ diff --git a/packages/orchestration/src/exos/orchestrator.js b/packages/orchestration/src/exos/orchestrator.js index 294a3bcffe7..1ffab8c2613 100644 --- a/packages/orchestration/src/exos/orchestrator.js +++ b/packages/orchestration/src/exos/orchestrator.js @@ -3,39 +3,33 @@ import { AmountShape } from '@agoric/ertp'; import { makeTracer } from '@agoric/internal'; import { V } from '@agoric/vow/vat.js'; import { M } from '@endo/patterns'; -// eslint-disable-next-line import/no-cycle -- FIXME -import { makeLocalChainFacade } from '../facade.js'; +import { + ChainInfoShape, + LocalChainAccountShape, + DenomShape, + BrandInfoShape, + DenomAmountShape, +} from '../typeGuards.js'; /** * @import {Zone} from '@agoric/base-zone'; * @import {ChainHub} from '../utils/chainHub.js'; - * @import {Connection, Port} from '@agoric/network'; - * @import {AnyJson} from '@agoric/cosmic-proto'; - * @import {TxBody} from '@agoric/cosmic-proto/cosmos/tx/v1beta1/tx.js'; - * @import {LocalIbcAddress, RemoteIbcAddress} from '@agoric/vats/tools/ibc-utils.js'; * @import {AsyncFlowTools} from '@agoric/async-flow'; * @import {Vow} from '@agoric/vow'; * @import {TimerService} from '@agoric/time'; - * @import {IBCConnectionID} from '@agoric/vats'; * @import {LocalChain} from '@agoric/vats/src/localchain.js'; * @import {RecorderKit, MakeRecorderKit} from '@agoric/zoe/src/contractSupport/recorder.js'. * @import {Remote} from '@agoric/internal'; * @import {OrchestrationService} from '../service.js'; - * @import {MakeLocalChainAccountKit} from './local-chain-account-kit.js'; + * @import {MakeLocalOrchestrationAccountKit} from './local-orchestration-account.js'; + * @import {MakeLocalChainFacade} from './local-chain-facade.js'; + * @import {MakeRemoteChainFacade} from './remote-chain-facade.js'; * @import {Chain, ChainInfo, CosmosChainInfo, IBCConnectionInfo, OrchestrationAccount, Orchestrator} from '../types.js'; */ const { Fail } = assert; const trace = makeTracer('Orchestrator'); -// TODO more validation -export const ChainInfoShape = M.any(); -export const LocalChainAccountShape = M.remotable('LocalChainAccount'); -export const DenomShape = M.string(); -export const BrandInfoShape = M.any(); - -export const DenomAmountShape = { denom: DenomShape, value: M.bigint() }; - /** @see {Orchestrator} */ export const OrchestratorI = M.interface('Orchestrator', { getChain: M.callWhen(M.string()).returns(ChainInfoShape), @@ -50,9 +44,9 @@ export const OrchestratorI = M.interface('Orchestrator', { * asyncFlowTools: AsyncFlowTools; * chainHub: ChainHub; * localchain: Remote; - * makeLocalChainAccountKit: MakeLocalChainAccountKit; * makeRecorderKit: MakeRecorderKit; - * makeRemoteChainFacade: any; + * makeLocalChainFacade: MakeLocalChainFacade; + * makeRemoteChainFacade: MakeRemoteChainFacade; * orchestrationService: Remote; * storageNode: Remote; * timerService: Remote; @@ -61,7 +55,7 @@ export const OrchestratorI = M.interface('Orchestrator', { */ export const prepareOrchestrator = ( zone, - { chainHub, localchain, makeLocalChainAccountKit, makeRemoteChainFacade }, + { chainHub, localchain, makeLocalChainFacade, makeRemoteChainFacade }, ) => zone.exoClass( 'Orchestrator', @@ -76,11 +70,8 @@ export const prepareOrchestrator = ( const agoricChainInfo = await chainHub.getChainInfo('agoric'); if (name === 'agoric') { - return makeLocalChainFacade( - localchain, - makeLocalChainAccountKit, - agoricChainInfo, - ); + // @ts-expect-error XXX chainInfo generic + return makeLocalChainFacade(agoricChainInfo); } const remoteChainInfo = await chainHub.getChainInfo(name); @@ -89,6 +80,7 @@ export const prepareOrchestrator = ( remoteChainInfo.chainId, ); + // @ts-expect-error XXX chainInfo generic return makeRemoteChainFacade(remoteChainInfo, connectionInfo); }, makeLocalAccount() { diff --git a/packages/orchestration/src/exos/remote-chain-facade.js b/packages/orchestration/src/exos/remote-chain-facade.js index bcf16cd573e..3ba96ca57fc 100644 --- a/packages/orchestration/src/exos/remote-chain-facade.js +++ b/packages/orchestration/src/exos/remote-chain-facade.js @@ -1,16 +1,15 @@ /** @file ChainAccount exo */ import { makeTracer } from '@agoric/internal'; import { V } from '@agoric/vow/vat.js'; -import { M } from '@endo/patterns'; -import { ChainInfoShape } from './orchestrator.js'; +import { ChainFacadeI } from '../typeGuards.js'; /** * @import {Zone} from '@agoric/base-zone'; * @import {TimerService} from '@agoric/time'; * @import {Remote} from '@agoric/internal'; * @import {OrchestrationService} from '../service.js'; - * @import {prepareCosmosOrchestrationAccount} from './cosmosOrchestrationAccount.js'; + * @import {prepareCosmosOrchestrationAccount} from './cosmos-orchestration-account.js'; * @import {ChainInfo, CosmosChainInfo, IBCConnectionInfo, OrchestrationAccount} from '../types.js'; */ @@ -20,12 +19,6 @@ const trace = makeTracer('RemoteChainFacade'); /** @type {any} */ const anyVal = null; -/** @see {Chain} */ -export const RemoteChainFacadeI = M.interface('RemoteChainFacade', { - getChainInfo: M.callWhen().returns(ChainInfoShape), - makeAccount: M.callWhen().returns(M.remotable('OrchestrationAccount')), -}); - /** * @param {Zone} zone * @param {{ @@ -43,7 +36,7 @@ export const prepareRemoteChainFacade = ( ) => zone.exoClass( 'RemoteChainFacade', - RemoteChainFacadeI, + ChainFacadeI, /** * @param {CosmosChainInfo} remoteChainInfo * @param {IBCConnectionInfo} connectionInfo @@ -71,15 +64,11 @@ export const prepareRemoteChainFacade = ( const address = await V(icaAccount).getAddress(); - const [{ denom: bondDenom }] = remoteChainInfo.stakingTokens || [ - { - denom: null, - }, - ]; - if (!bondDenom) { - throw Fail`missing bondDenom`; + const stakingDenom = remoteChainInfo.stakingTokens?.[0]?.denom; + if (!stakingDenom) { + throw Fail`chain info lacks staking denom`; } - return makeCosmosOrchestrationAccount(address, bondDenom, { + return makeCosmosOrchestrationAccount(address, stakingDenom, { account: icaAccount, storageNode, icqConnection: anyVal, @@ -89,3 +78,4 @@ export const prepareRemoteChainFacade = ( }, ); harden(prepareRemoteChainFacade); +/** @typedef {ReturnType} MakeRemoteChainFacade */ diff --git a/packages/orchestration/src/facade.js b/packages/orchestration/src/facade.js index 8fe45036f7c..2043346b14f 100644 --- a/packages/orchestration/src/facade.js +++ b/packages/orchestration/src/facade.js @@ -1,9 +1,7 @@ /** @file Orchestration service */ import { Fail } from '@agoric/assert'; -import { V as E } from '@agoric/vow/vat.js'; -import { Far } from '@endo/far'; -// eslint-disable-next-line import/no-cycle -- FIXME + import { prepareOrchestrator } from './exos/orchestrator.js'; /** @@ -17,83 +15,11 @@ import { prepareOrchestrator } from './exos/orchestrator.js'; * @import {Remote} from '@agoric/internal'; * @import {OrchestrationService} from './service.js'; * @import {Chain, ChainInfo, CosmosChainInfo, IBCConnectionInfo, OrchestrationAccount, Orchestrator} from './types.js'; + * @import {MakeLocalChainFacade} from './exos/local-chain-facade.js'; + * @import {MakeRemoteChainFacade} from './exos/remote-chain-facade.js'; + * @import {MakeLocalOrchestrationAccountKit} from './exos/local-orchestration-account.js'; */ -// FIXME turn this into an Exo -/** - * @param {Remote} localchain - * @param {ReturnType< - * typeof import('./exos/local-chain-account-kit.js').prepareLocalChainAccountKit - * >} makeLocalChainAccountKit - * @param {ChainInfo} localInfo - * @returns {Chain} - */ -export const makeLocalChainFacade = ( - localchain, - makeLocalChainAccountKit, - localInfo, -) => { - return Far('LocalChainFacade', { - /** @returns {Promise} */ - async getChainInfo() { - return localInfo; - }, - - async makeAccount() { - const lcaP = E(localchain).makeAccount(); - const [lca, address] = await Promise.all([lcaP, E(lcaP).getAddress()]); - const { holder: account } = makeLocalChainAccountKit({ - account: lca, - address, - // @ts-expect-error TODO: Remote - storageNode: null, - }); - - // FIXME turn this into an Exo LocalChainOrchestrationAccount or make that a facet of makeLocalChainAccountKit - return { - async deposit(payment) { - console.log('deposit got', payment); - await E(account).deposit(payment); - }, - getAddress() { - const addressStr = account.getAddress(); - return { - address: addressStr, - chainId: localInfo.chainId, - addressEncoding: 'bech32', - }; - }, - async getBalance(denomArg) { - // FIXME look up real values - // UNTIL https://github.com/Agoric/agoric-sdk/issues/9211 - const [brand, denom] = - typeof denomArg === 'string' - ? [/** @type {any} */ (null), denomArg] - : [denomArg, 'FIXME']; - - const natAmount = await E(lca).getBalance(brand); - return harden({ denom, value: natAmount.value }); - }, - getBalances() { - throw new Error('not yet implemented'); - }, - async send(toAccount, amount) { - // FIXME implement - console.log('send got', toAccount, amount); - }, - async transfer(amount, destination, opts) { - console.log('transfer got', amount, destination, opts); - return account.transfer(amount, destination, opts); - }, - transferSteps(amount, msg) { - console.log('transferSteps got', amount, msg); - return Promise.resolve(); - }, - }; - }, - }); -}; - /** * @param {{ * zone: Zone; @@ -103,12 +29,11 @@ export const makeLocalChainFacade = ( * orchestrationService: Remote; * localchain: Remote; * chainHub: import('./utils/chainHub.js').ChainHub; - * makeLocalChainAccountKit: ReturnType< - * typeof import('./exos/local-chain-account-kit.js').prepareLocalChainAccountKit - * >; + * makeLocalOrchestrationAccountKit: MakeLocalOrchestrationAccountKit; * makeRecorderKit: MakeRecorderKit; * makeCosmosOrchestrationAccount: any; - * makeRemoteChainFacade: any; + * makeLocalChainFacade: MakeLocalChainFacade; + * makeRemoteChainFacade: MakeRemoteChainFacade; * asyncFlowTools: AsyncFlowTools; * }} powers */ @@ -120,8 +45,9 @@ export const makeOrchestrationFacade = ({ orchestrationService, localchain, chainHub, - makeLocalChainAccountKit, + makeLocalOrchestrationAccountKit, makeRecorderKit, + makeLocalChainFacade, makeRemoteChainFacade, asyncFlowTools, }) => { @@ -131,9 +57,10 @@ export const makeOrchestrationFacade = ({ storageNode && orchestrationService && // @ts-expect-error type says defined but double check - makeLocalChainAccountKit && + makeLocalOrchestrationAccountKit && // @ts-expect-error type says defined but double check makeRecorderKit && + // @ts-expect-error type says defined but double check makeRemoteChainFacade && asyncFlowTools) || Fail`params missing`; @@ -142,8 +69,8 @@ export const makeOrchestrationFacade = ({ asyncFlowTools, chainHub, localchain, - makeLocalChainAccountKit, makeRecorderKit, + makeLocalChainFacade, makeRemoteChainFacade, orchestrationService, storageNode, diff --git a/packages/orchestration/src/service.js b/packages/orchestration/src/service.js index 3c203914296..39fc4fa9c41 100644 --- a/packages/orchestration/src/service.js +++ b/packages/orchestration/src/service.js @@ -3,8 +3,8 @@ import { V as E } from '@agoric/vow/vat.js'; import { M } from '@endo/patterns'; import { Shape as NetworkShape } from '@agoric/network'; -import { prepareChainAccountKit } from './exos/chainAccountKit.js'; -import { prepareICQConnectionKit } from './exos/icqConnectionKit.js'; +import { prepareChainAccountKit } from './exos/chain-account-kit.js'; +import { prepareICQConnectionKit } from './exos/icq-connection-kit.js'; import { makeICAChannelAddress, makeICQChannelAddress, @@ -133,7 +133,7 @@ const prepareOrchestrationKit = ( remoteConnAddr, chainAccountKit.connectionHandler, ); - // XXX if we fail, should we close the port (if it was created in this flow)? + // FIXME if we fail, should we close the port (if it was created in this flow)? return chainAccountKit.account; }, /** diff --git a/packages/orchestration/src/typeGuards.js b/packages/orchestration/src/typeGuards.js index e3a836cf2dc..9a4a7391c21 100644 --- a/packages/orchestration/src/typeGuards.js +++ b/packages/orchestration/src/typeGuards.js @@ -77,3 +77,18 @@ export const CosmosChainInfoShape = M.splitRecord( icqEnabled: M.boolean(), }, ); + +// FIXME more validation +export const ChainInfoShape = M.any(); +export const LocalChainAccountShape = M.remotable('LocalChainAccount'); +export const DenomShape = M.string(); +// FIXME more validation +export const BrandInfoShape = M.any(); + +export const DenomAmountShape = { denom: DenomShape, value: M.bigint() }; + +/** @see {Chain} */ +export const ChainFacadeI = M.interface('ChainFacade', { + getChainInfo: M.callWhen().returns(ChainInfoShape), + makeAccount: M.callWhen().returns(M.remotable('OrchestrationAccount')), +}); diff --git a/packages/orchestration/src/types.ts b/packages/orchestration/src/types.ts index 151bf3bb83b..42c0f81af80 100644 --- a/packages/orchestration/src/types.ts +++ b/packages/orchestration/src/types.ts @@ -3,8 +3,8 @@ export type * from './chain-info.js'; export type * from './cosmos-api.js'; export type * from './ethereum-api.js'; -export type * from './exos/chainAccountKit.js'; -export type * from './exos/icqConnectionKit.js'; +export type * from './exos/chain-account-kit.js'; +export type * from './exos/icq-connection-kit.js'; export type * from './orchestration-api.js'; export type * from './service.js'; export type * from './vat-orchestration.js'; diff --git a/packages/orchestration/src/utils/chainHub.js b/packages/orchestration/src/utils/chainHub.js index d1df713d5c1..11323e4c090 100644 --- a/packages/orchestration/src/utils/chainHub.js +++ b/packages/orchestration/src/utils/chainHub.js @@ -163,7 +163,7 @@ export const registerChain = async ( .then(() => log(`registered agoricNames chain.${name}`)), ]; - // XXX updates redundantly, twice per edge + // FIXME updates redundantly, twice per edge for await (const [counterChainId, connInfo] of Object.entries(connections)) { const key = connectionKey(chainInfo.chainId, counterChainId); promises.push( diff --git a/packages/orchestration/src/utils/orc.js b/packages/orchestration/src/utils/orc.js index e4dd7895216..80333accc44 100644 --- a/packages/orchestration/src/utils/orc.js +++ b/packages/orchestration/src/utils/orc.js @@ -8,7 +8,7 @@ export const orcUtils = { * @returns {TransferMsg} */ makeTransferMsg: _args => { - // XXX mocked, so typescript is happy + // FIXME mocked, so typescript is happy return { toAccount: { chainId: 'osmosis-test', @@ -26,7 +26,7 @@ export const orcUtils = { * @returns {TransferMsg} */ makeOsmosisSwap(_args) { - // XXX mocked, so typescript is happy + // FIXME mocked, so typescript is happy return { toAccount: { chainId: 'osmosis-test', diff --git a/packages/orchestration/src/utils/orchestrationAccount.js b/packages/orchestration/src/utils/orchestrationAccount.js index b5b325980c1..b4824919706 100644 --- a/packages/orchestration/src/utils/orchestrationAccount.js +++ b/packages/orchestration/src/utils/orchestrationAccount.js @@ -4,16 +4,15 @@ import { AmountArgShape, ChainAddressShape, CoinShape } from '../typeGuards.js'; /** @import {OrchestrationAccountI} from '../orchestration-api.js'; */ -// TODO complete this interface /** @see {OrchestrationAccountI} */ export const orchestrationAccountMethods = { getAddress: M.call().returns(ChainAddressShape), getBalance: M.callWhen(M.any()).returns(CoinShape), getBalances: M.callWhen().returns(M.arrayOf(CoinShape)), send: M.callWhen(ChainAddressShape, AmountArgShape).returns(M.undefined()), - transfer: M.callWhen(AmountArgShape, ChainAddressShape).returns( - M.undefined(), - ), + transfer: M.callWhen(AmountArgShape, ChainAddressShape) + .optional(M.record()) + .returns(M.undefined()), transferSteps: M.callWhen(AmountArgShape, M.any()).returns(M.undefined()), deposit: M.callWhen(PaymentShape).returns(M.undefined()), }; diff --git a/packages/orchestration/src/utils/start-helper.js b/packages/orchestration/src/utils/start-helper.js index 6fcf3fb8d36..7b3f2c6fb74 100644 --- a/packages/orchestration/src/utils/start-helper.js +++ b/packages/orchestration/src/utils/start-helper.js @@ -2,11 +2,12 @@ import { prepareAsyncFlowTools } from '@agoric/async-flow'; import { prepareVowTools } from '@agoric/vow'; import { prepareRecorderKitMakers } from '@agoric/zoe/src/contractSupport/recorder.js'; import { makeDurableZone } from '@agoric/zone/durable.js'; -import { prepareLocalChainAccountKit } from '../exos/local-chain-account-kit.js'; +import { prepareLocalOrchestrationAccountKit } from '../exos/local-orchestration-account.js'; import { makeOrchestrationFacade } from '../facade.js'; import { makeChainHub } from './chainHub.js'; import { prepareRemoteChainFacade } from '../exos/remote-chain-facade.js'; -import { prepareCosmosOrchestrationAccount } from '../exos/cosmosOrchestrationAccount.js'; +import { prepareCosmosOrchestrationAccount } from '../exos/cosmos-orchestration-account.js'; +import { prepareLocalChainFacade } from '../exos/local-chain-facade.js'; /** * @import {PromiseKit} from '@endo/promise-kit' @@ -48,7 +49,7 @@ export const provideOrchestration = ( const chainHub = makeChainHub(remotePowers.agoricNames); const { makeRecorderKit } = prepareRecorderKitMakers(baggage, marshaller); - const makeLocalChainAccountKit = prepareLocalChainAccountKit( + const makeLocalOrchestrationAccountKit = prepareLocalOrchestrationAccountKit( zone, makeRecorderKit, zcf, @@ -62,7 +63,7 @@ export const provideOrchestration = ( }); const makeCosmosOrchestrationAccount = prepareCosmosOrchestrationAccount( - // XXX what zone? + // FIXME what zone? zone, makeRecorderKit, zcf, @@ -75,13 +76,23 @@ export const provideOrchestration = ( timer: remotePowers.timerService, }); + const makeLocalChainFacade = prepareLocalChainFacade(zone, { + makeLocalOrchestrationAccountKit, + localchain: remotePowers.localchain, + // FIXME what path? + storageNode: remotePowers.storageNode, + orchestration: remotePowers.orchestrationService, + timer: remotePowers.timerService, + }); + const facade = makeOrchestrationFacade({ zcf, zone, chainHub, - makeLocalChainAccountKit, + makeLocalOrchestrationAccountKit, makeRecorderKit, makeCosmosOrchestrationAccount, + makeLocalChainFacade, makeRemoteChainFacade, asyncFlowTools, ...remotePowers, diff --git a/packages/orchestration/test/examples/stake-bld.contract.test.ts b/packages/orchestration/test/examples/stake-bld.contract.test.ts index 489afa865a3..db6ef562ab4 100644 --- a/packages/orchestration/test/examples/stake-bld.contract.test.ts +++ b/packages/orchestration/test/examples/stake-bld.contract.test.ts @@ -51,15 +51,13 @@ test('makeAccount, deposit, withdraw', async t => { t.log('make a LocalChainAccount'); const account = await E(publicFacet).makeAccount(); t.truthy(account, 'account is returned'); - t.regex(await E(account).getAddress(), /agoric1/); t.log('deposit 100 bld to account'); const depositResp = await V(account).deposit( await utils.pourPayment(bld.units(100)), ); - t.true(AmountMath.isEqual(depositResp, bld.units(100)), 'deposit'); - - // TODO validate balance, .getBalance() + // FIXME #9211 + // t.deepEqual(await E(account).getBalance('ubld'), bld.units(100)); t.log('withdraw bld from account'); const withdrawResp = await V(account).withdraw(bld.units(100)); diff --git a/packages/orchestration/test/exos/local-chain-account-kit.test.ts b/packages/orchestration/test/exos/local-orchestration-account-kit.test.ts similarity index 85% rename from packages/orchestration/test/exos/local-chain-account-kit.test.ts rename to packages/orchestration/test/exos/local-orchestration-account-kit.test.ts index 88547d4f22f..b3e91a264b3 100644 --- a/packages/orchestration/test/exos/local-chain-account-kit.test.ts +++ b/packages/orchestration/test/exos/local-orchestration-account-kit.test.ts @@ -5,7 +5,7 @@ import { prepareRecorderKitMakers } from '@agoric/zoe/src/contractSupport/record import { V as E } from '@agoric/vow/vat.js'; import { Far } from '@endo/far'; import { commonSetup } from '../supports.js'; -import { prepareLocalChainAccountKit } from '../../src/exos/local-chain-account-kit.js'; +import { prepareLocalOrchestrationAccountKit } from '../../src/exos/local-orchestration-account.js'; import { ChainAddress } from '../../src/orchestration-api.js'; import { NANOSECONDS_PER_SECOND } from '../../src/utils/time.js'; import { makeChainHub } from '../../src/utils/chainHub.js'; @@ -24,7 +24,7 @@ test('deposit, withdraw', async t => { rootZone.mapStore('recorder'), marshaller, ); - const makeLocalChainAccountKit = prepareLocalChainAccountKit( + const makeLocalOrchestrationAccountKit = prepareLocalOrchestrationAccountKit( rootZone, makeRecorderKit, // @ts-expect-error mocked zcf. use `stake-bld.contract.test.ts` to test LCA with offer @@ -38,19 +38,22 @@ test('deposit, withdraw', async t => { const address = await E(lca).getAddress(); t.log('make a LocalChainAccountKit'); - const { holder: account } = makeLocalChainAccountKit({ + const { holder: account } = makeLocalOrchestrationAccountKit({ account: lca, - address, + address: harden({ + address, + chainId: 'agoric-n', + addressEncoding: 'bech32', + }), storageNode: storage.rootNode.makeChildNode('lcaKit'), }); - t.regex(await E(account).getAddress(), /agoric1/); - const oneHundredStakePmt = await utils.pourPayment(stake.units(100)); t.log('deposit 100 bld to account'); const depositResp = await E(account).deposit(oneHundredStakePmt); - t.true(AmountMath.isEqual(depositResp, stake.units(100)), 'deposit'); + // FIXME #9211 + // t.deepEqual(await E(account).getBalance('ubld'), stake.units(100)); const withdrawal1 = await E(account).withdraw(stake.units(50)); t.true( @@ -88,7 +91,7 @@ test('delegate, undelegate', async t => { rootZone.mapStore('recorder'), marshaller, ); - const makeLocalChainAccountKit = prepareLocalChainAccountKit( + const makeLocalOrchestrationAccountKit = prepareLocalOrchestrationAccountKit( rootZone, makeRecorderKit, // @ts-expect-error mocked zcf. use `stake-bld.contract.test.ts` to test LCA with offer @@ -102,14 +105,16 @@ test('delegate, undelegate', async t => { const address = await E(lca).getAddress(); t.log('make a LocalChainAccountKit'); - const { holder: account } = makeLocalChainAccountKit({ + const { holder: account } = makeLocalOrchestrationAccountKit({ account: lca, - address, + address: harden({ + address, + chainId: 'agoric-n', + addressEncoding: 'bech32', + }), storageNode: storage.rootNode.makeChildNode('lcaKit'), }); - t.regex(await E(account).getAddress(), /agoric1/); - await E(account).deposit(await utils.pourPayment(bld.units(100))); const validatorAddress = 'agoric1validator1'; @@ -121,6 +126,7 @@ test('delegate, undelegate', async t => { await E(account).delegate(validatorAddress, bld.units(999)); // TODO get the timer to fire so that this promise resolves void E(account).undelegate(validatorAddress, bld.units(999)); + t.pass(); }); test('transfer', async t => { @@ -135,7 +141,7 @@ test('transfer', async t => { rootZone.mapStore('recorder'), marshaller, ); - const makeLocalChainAccountKit = prepareLocalChainAccountKit( + const makeLocalOrchestrationAccountKit = prepareLocalOrchestrationAccountKit( rootZone, makeRecorderKit, // @ts-expect-error mocked zcf. use `stake-bld.contract.test.ts` to test LCA with offer @@ -149,20 +155,24 @@ test('transfer', async t => { const address = await E(lca).getAddress(); t.log('make a LocalChainAccountKit'); - const { holder: account } = makeLocalChainAccountKit({ + const { holder: account } = makeLocalOrchestrationAccountKit({ account: lca, - address, + address: harden({ + address, + chainId: 'agoric-n', + addressEncoding: 'bech32', + }), storageNode: storage.rootNode.makeChildNode('lcaKit'), }); t.truthy(account, 'account is returned'); - t.regex(await E(account).getAddress(), /agoric1/); const oneHundredStakePmt = await utils.pourPayment(stake.units(100)); t.log('deposit 100 bld to account'); - const depositResp = await E(account).deposit(oneHundredStakePmt); - t.true(AmountMath.isEqual(depositResp, stake.units(100)), 'deposit'); + await E(account).deposit(oneHundredStakePmt); + // FIXME #9211 + // t.deepEqual(await E(account).getBalance('ubld'), stake.units(100)); const destination: ChainAddress = { chainId: 'cosmoshub-4', diff --git a/packages/orchestration/test/facade.test.ts b/packages/orchestration/test/facade.test.ts index bc5e4c915d2..5ccb3206e9a 100644 --- a/packages/orchestration/test/facade.test.ts +++ b/packages/orchestration/test/facade.test.ts @@ -38,7 +38,7 @@ export const mockChainConnection: IBCConnectionInfo = { }, }; -const makeLocalChainAccountKit = () => assert.fail(`not used`); +const makeLocalOrchestrationAccountKit = () => assert.fail(`not used`); test('chain info', async t => { const { bootstrap, facadeServices, commonPrivateArgs } = await commonSetup(t); diff --git a/packages/orchestration/test/staking-ops.test.ts b/packages/orchestration/test/staking-ops.test.ts index f6eb1bd8e9a..8bbe71c6388 100644 --- a/packages/orchestration/test/staking-ops.test.ts +++ b/packages/orchestration/test/staking-ops.test.ts @@ -23,7 +23,7 @@ import { prepareRecorderKitMakers } from '@agoric/zoe/src/contractSupport/record import { prepareCosmosOrchestrationAccountKit, trivialDelegateResponse, -} from '../src/exos/cosmosOrchestrationAccount.js'; +} from '../src/exos/cosmos-orchestration-account.js'; import { encodeTxResponse } from '../src/utils/cosmos.js'; import type { IcaAccount, ChainAddress, ICQConnection } from '../src/types.js'; diff --git a/packages/orchestration/test/types.test-d.ts b/packages/orchestration/test/types.test-d.ts index 37095f7538d..cfc5989588b 100644 --- a/packages/orchestration/test/types.test-d.ts +++ b/packages/orchestration/test/types.test-d.ts @@ -10,8 +10,8 @@ import type { CosmosValidatorAddress, StakingAccountActions, } from '../src/types.js'; -import type { LocalChainAccountKit } from '../src/exos/local-chain-account-kit.js'; -import { prepareCosmosOrchestrationAccount } from '../src/exos/cosmosOrchestrationAccount.js'; +import type { LocalOrchestrationAccountKit } from '../src/exos/local-orchestration-account.js'; +import { prepareCosmosOrchestrationAccount } from '../src/exos/cosmos-orchestration-account.js'; const anyVal = null as any; @@ -36,7 +36,7 @@ expectNotType(chainAddr); } { - const lcak: LocalChainAccountKit = null as any; + const lcak: LocalOrchestrationAccountKit = null as any; const results = await lcak.holder.executeTx([ typedJson('/cosmos.staking.v1beta1.MsgDelegate', { amount: {