diff --git a/a3p-integration/proposals/c:stake-bld/stakeBld.test.js b/a3p-integration/proposals/c:stake-bld/stakeBld.test.js index 13e6cb52c1f..352db45415d 100644 --- a/a3p-integration/proposals/c:stake-bld/stakeBld.test.js +++ b/a3p-integration/proposals/c:stake-bld/stakeBld.test.js @@ -66,10 +66,33 @@ test('basic', async t => { }, }); - const postDelegation = await currentDelegation(); - t.is(postDelegation.length, 2, 'new delegation now'); - t.like(postDelegation[1], { + const afterDelegate = await currentDelegation(); + t.is(afterDelegate.length, 2, 'delegations after delegation'); + t.like(afterDelegate[1], { balance: { amount: '10', denom: 'ubld' }, // omit 'delegation' because it has 'delegatorAddress' which is different every test run }); + + await walletUtils.broadcastBridgeAction(GOV1ADDR, { + method: 'executeOffer', + offer: { + id: 'request-undelegate', + invitationSpec: { + source: 'continuing', + previousOffer: 'request-stake', + invitationMakerName: 'Undelegate', + invitationArgs: [VALIDATOR_ADDRESS, { brand: brand.BLD, value: 5n }], + }, + proposal: {}, + }, + }); + + // TODO wait until completionTime, so we can check the count has gone back down + // https://github.com/Agoric/agoric-sdk/pull/9439#discussion_r1626451871 + + // const afterUndelegate = await currentDelegation(); + // t.is(afterDelegate.length, 1, 'delegations after undelegation'); + // t.like(afterUndelegate[1], { + // balance: { amount: '5', denom: 'ubld' }, + // }); }); diff --git a/packages/boot/test/bootstrapTests/orchestration.test.ts b/packages/boot/test/bootstrapTests/orchestration.test.ts index 17a2ddc18a8..ddc95ce7c2f 100644 --- a/packages/boot/test/bootstrapTests/orchestration.test.ts +++ b/packages/boot/test/bootstrapTests/orchestration.test.ts @@ -62,7 +62,7 @@ test.serial('stakeAtom - repl-style', async t => { }; await t.notThrowsAsync(EV(account).delegate(validatorAddress, atomAmount)); - const queryRes = await EV(account).getBalance(); + const queryRes = await EV(account).getBalance('uatom'); t.deepEqual(queryRes, { value: 0n, denom: 'uatom' }); const queryUnknownDenom = await EV(account).getBalance('some-invalid-denom'); diff --git a/packages/orchestration/src/chain-info.ts b/packages/orchestration/src/chain-info.ts index bb729352588..c916d8f8019 100644 --- a/packages/orchestration/src/chain-info.ts +++ b/packages/orchestration/src/chain-info.ts @@ -13,10 +13,12 @@ import type { StakingAccountQueries, } from './types.js'; +export type ChainInfo = CosmosChainInfo | EthChainInfo; + // TODO generate this automatically with a build script drawing on data sources such as https://github.com/cosmos/chain-registry // XXX methods ad-hoc and not fully accurate -export type KnownChains = { +export type KnownChains = Record & { stride: { info: CosmosChainInfo; methods: IcaAccount & @@ -60,5 +62,3 @@ export type KnownChains = { StakingAccountQueries; }; }; - -export type ChainInfo = CosmosChainInfo | EthChainInfo; diff --git a/packages/orchestration/src/cosmos-api.ts b/packages/orchestration/src/cosmos-api.ts index 2a81bc261e2..134628e4462 100644 --- a/packages/orchestration/src/cosmos-api.ts +++ b/packages/orchestration/src/cosmos-api.ts @@ -67,7 +67,7 @@ export type IBCConnectionInfo = { */ export type CosmosChainInfo = { chainId: string; - connections: MapStore; // chainId or wellKnownName + connections: Record; // chainId or wellKnownName icaEnabled: boolean; icqEnabled: boolean; pfmEnabled: boolean; @@ -196,8 +196,6 @@ export interface IcaAccount { msgs: AnyJson[], opts?: Partial>, ) => Promise; - /** deposit payment from zoe to the account*/ - deposit: (payment: Payment) => Promise; /** get Purse for a brand to .withdraw() a Payment from the account */ getPurse: (brand: Brand) => Promise; /** diff --git a/packages/orchestration/src/examples/stakeAtom.contract.js b/packages/orchestration/src/examples/stakeAtom.contract.js index 9d09d025912..eb5812c805d 100644 --- a/packages/orchestration/src/examples/stakeAtom.contract.js +++ b/packages/orchestration/src/examples/stakeAtom.contract.js @@ -1,6 +1,5 @@ -/** - * @file Example contract that uses orchestration - */ +/** @file Example contract that uses orchestration */ +// TODO rename to "stakeIca" or something else that conveys is parameterized nature import { makeTracer, StorageNodeShape } from '@agoric/internal'; import { TimerServiceShape } from '@agoric/time'; @@ -9,7 +8,7 @@ import { prepareRecorderKitMakers } from '@agoric/zoe/src/contractSupport'; import { InvitationShape } from '@agoric/zoe/src/typeGuards.js'; import { makeDurableZone } from '@agoric/zone/durable.js'; import { M } from '@endo/patterns'; -import { prepareStakingAccountKit } from '../exos/stakingAccountKit.js'; +import { prepareCosmosOrchestrationAccount } from '../exos/cosmosOrchestrationAccount.js'; const trace = makeTracer('StakeAtom'); /** @@ -20,6 +19,11 @@ const trace = makeTracer('StakeAtom'); */ export const meta = harden({ + customTermsShape: { + hostConnectionId: M.string(), + controllerConnectionId: M.string(), + bondDenom: M.string(), + }, privateArgsShape: { orchestration: M.remotable('orchestration'), storageNode: StorageNodeShape, @@ -57,7 +61,7 @@ export const start = async (zcf, privateArgs, baggage) => { const { makeRecorderKit } = prepareRecorderKitMakers(baggage, marshaller); - const makeStakingAccountKit = prepareStakingAccountKit( + const makeCosmosOrchestrationAccount = prepareCosmosOrchestrationAccount( zone, makeRecorderKit, zcf, @@ -68,28 +72,21 @@ export const start = async (zcf, privateArgs, baggage) => { hostConnectionId, controllerConnectionId, ); - // #9212 TODO do not fail if host does not have `async-icq` module; + // TODO https://github.com/Agoric/agoric-sdk/issues/9326 + // Should not fail if host does not have `async-icq` module; // communicate to OrchestrationAccount that it can't send queries const icqConnection = await E(orchestration).provideICQConnection( controllerConnectionId, ); const accountAddress = await E(account).getAddress(); trace('account address', accountAddress); - const { holder, invitationMakers } = makeStakingAccountKit( - accountAddress, - bondDenom, - { - account, - storageNode, - icqConnection, - timer, - }, - ); - return { - publicSubscribers: holder.getPublicTopics(), - invitationMakers, - account: holder, - }; + const holder = makeCosmosOrchestrationAccount(accountAddress, bondDenom, { + account, + storageNode, + icqConnection, + timer, + }); + return holder; } const publicFacet = zone.exo( @@ -101,15 +98,15 @@ export const start = async (zcf, privateArgs, baggage) => { { async makeAccount() { trace('makeAccount'); - const { account } = await makeAccountKit(); - return account; + return makeAccountKit(); }, makeAccountInvitationMaker() { trace('makeCreateAccountInvitation'); return zcf.makeInvitation( async seat => { seat.exit(); - return makeAccountKit(); + const holder = await makeAccountKit(); + return holder.asContinuingOffer(); }, 'wantStakingAccount', undefined, diff --git a/packages/orchestration/src/examples/stakeBld.contract.js b/packages/orchestration/src/examples/stakeBld.contract.js index c51b2544ccf..b2ae4d1dfe2 100644 --- a/packages/orchestration/src/examples/stakeBld.contract.js +++ b/packages/orchestration/src/examples/stakeBld.contract.js @@ -44,7 +44,7 @@ export const start = async (zcf, privateArgs, baggage) => { // Mocked until #8879 // Would expect this to be instantiated elsewhere, and passed in as a reference - const agoricChainInfo = prepareMockChainInfo(zone); + const agoricChainInfo = prepareMockChainInfo(); const makeLocalChainAccountKit = prepareLocalChainAccountKit( zone, diff --git a/packages/orchestration/src/examples/swapExample.contract.js b/packages/orchestration/src/examples/swapExample.contract.js index 1557a5da778..8fdb4ac49de 100644 --- a/packages/orchestration/src/examples/swapExample.contract.js +++ b/packages/orchestration/src/examples/swapExample.contract.js @@ -1,6 +1,7 @@ import { StorageNodeShape } from '@agoric/internal'; import { TimerServiceShape } from '@agoric/time'; import { withdrawFromSeat } from '@agoric/zoe/src/contractSupport/zoeHelpers.js'; +import { makeDurableZone } from '@agoric/zone/durable.js'; import { Far } from '@endo/far'; import { deeplyFulfilled } from '@endo/marshal'; import { M, objectMap } from '@endo/patterns'; @@ -13,6 +14,7 @@ import { orcUtils } from '../utils/orc.js'; * @import {LocalChain} from '@agoric/vats/src/localchain.js'; * @import {Remote} from '@agoric/internal'; * @import {OrchestrationService} from '../service.js'; + * @import {Baggage} from '@agoric/vat-data' * @import {Zone} from '@agoric/zone'; */ @@ -23,7 +25,6 @@ export const meta = { orchestrationService: M.or(M.remotable('orchestration'), null), storageNode: StorageNodeShape, timerService: M.or(TimerServiceShape, null), - zone: M.any(), }, upgradability: 'canUpgrade', }; @@ -42,16 +43,18 @@ export const makeNatAmountShape = (brand, min) => * @param {ZCF} zcf * @param {{ * localchain: Remote; - * orchestrationService: Remote | null; + * orchestrationService: Remote; * storageNode: Remote; - * timerService: Remote | null; - * zone: Zone; + * timerService: Remote; * }} privateArgs + * @param {Baggage} baggage */ -export const start = async (zcf, privateArgs) => { +export const start = async (zcf, privateArgs, baggage) => { const { brands } = zcf.getTerms(); - const { localchain, orchestrationService, storageNode, timerService, zone } = + const zone = makeDurableZone(baggage); + + const { localchain, orchestrationService, storageNode, timerService } = privateArgs; const { orchestrate } = makeOrchestrationFacade({ diff --git a/packages/orchestration/src/examples/unbondExample.contract.js b/packages/orchestration/src/examples/unbondExample.contract.js index fdddab58908..48a2a1979d5 100644 --- a/packages/orchestration/src/examples/unbondExample.contract.js +++ b/packages/orchestration/src/examples/unbondExample.contract.js @@ -1,6 +1,6 @@ +import { makeDurableZone } from '@agoric/zone/durable.js'; import { Far } from '@endo/far'; import { M } from '@endo/patterns'; -import { makeDurableZone } from '@agoric/zone/durable.js'; import { makeOrchestrationFacade } from '../facade.js'; /** @@ -49,19 +49,21 @@ export const start = async (zcf, privateArgs, baggage) => { const celestia = await orch.getChain('celestia'); const celestiaAccount = await celestia.makeAccount(); - const delegations = await celestiaAccount.getDelegations(); - // wait for the undelegations to be complete (may take weeks) - await celestiaAccount.undelegate(delegations); + // 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()); + // const tiaAmt = await celestiaAccount.getBalance('TIA'); + // await celestiaAccount.transfer(tiaAmt, strideAccount.getAddress()); - await strideAccount.liquidStake(tiaAmt); + // await strideAccount.liquidStake(tiaAmt); + console.log(celestiaAccount, strideAccount); }, ); diff --git a/packages/orchestration/src/exos/chainAccountKit.js b/packages/orchestration/src/exos/chainAccountKit.js index 34674106aef..01f3d92afe5 100644 --- a/packages/orchestration/src/exos/chainAccountKit.js +++ b/packages/orchestration/src/exos/chainAccountKit.js @@ -1,15 +1,15 @@ /** @file ChainAccount exo */ import { NonNullish } from '@agoric/assert'; +import { PurseShape } from '@agoric/ertp'; import { makeTracer } from '@agoric/internal'; import { V as E } from '@agoric/vow/vat.js'; import { M } from '@endo/patterns'; -import { PaymentShape, PurseShape } from '@agoric/ertp'; -import { findAddressField } from '../utils/address.js'; import { - ConnectionHandlerI, ChainAddressShape, + ConnectionHandlerI, Proto3Shape, } from '../typeGuards.js'; +import { findAddressField } from '../utils/address.js'; import { makeTxPacket, parseTxPacket } from '../utils/packet.js'; /** @@ -30,6 +30,8 @@ const UNPARSABLE_CHAIN_ADDRESS = 'UNPARSABLE_CHAIN_ADDRESS'; export const ChainAccountI = M.interface('ChainAccount', { getAddress: M.call().returns(ChainAddressShape), + getBalance: M.callWhen(M.string()).returns(M.any()), + getBalances: M.callWhen().returns(M.any()), getLocalAddress: M.call().returns(M.string()), getRemoteAddress: M.call().returns(M.string()), getPort: M.call().returns(M.remotable('Port')), @@ -38,7 +40,6 @@ export const ChainAccountI = M.interface('ChainAccount', { .optional(M.record()) .returns(M.promise()), close: M.callWhen().returns(M.undefined()), - deposit: M.callWhen(PaymentShape).returns(M.undefined()), getPurse: M.callWhen().returns(PurseShape), }); @@ -63,27 +64,31 @@ export const prepareChainAccountKit = zone => * @param {string} requestedRemoteAddress */ (port, requestedRemoteAddress) => - /** @type {State} */ ( - harden({ - port, - connection: undefined, - requestedRemoteAddress, - remoteAddress: undefined, - chainAddress: undefined, - localAddress: undefined, - }) - ), + /** @type {State} */ ({ + port, + connection: undefined, + requestedRemoteAddress, + remoteAddress: undefined, + chainAddress: undefined, + localAddress: undefined, + }), { account: { - /** - * @returns {ChainAddress} - */ + /** @returns {ChainAddress} */ getAddress() { return NonNullish( this.state.chainAddress, 'ICA channel creation acknowledgement not yet received.', ); }, + getBalance(_denom) { + // UNTIL https://github.com/Agoric/agoric-sdk/issues/9326 + throw new Error('not yet implemented'); + }, + getBalances() { + // UNTIL https://github.com/Agoric/agoric-sdk/issues/9326 + throw new Error('not yet implemented'); + }, getLocalAddress() { return NonNullish( this.state.localAddress, @@ -121,9 +126,7 @@ export const prepareChainAccountKit = zone => ack => parseTxPacket(ack), ); }, - /** - * Close the remote account - */ + /** Close the remote account */ async close() { /// XXX what should the behavior be here? and `onClose`? // - retrieve assets? @@ -132,10 +135,6 @@ export const prepareChainAccountKit = zone => if (!connection) throw Fail`connection not available`; await E(connection).close(); }, - async deposit(payment) { - console.log('deposit got', payment); - throw new Error('not yet implemented'); - }, /** * get Purse for a brand to .withdraw() a Payment from the account * diff --git a/packages/orchestration/src/exos/stakingAccountKit.js b/packages/orchestration/src/exos/cosmosOrchestrationAccount.js similarity index 81% rename from packages/orchestration/src/exos/stakingAccountKit.js rename to packages/orchestration/src/exos/cosmosOrchestrationAccount.js index 1461df5f850..f1c10cb24ac 100644 --- a/packages/orchestration/src/exos/stakingAccountKit.js +++ b/packages/orchestration/src/exos/cosmosOrchestrationAccount.js @@ -27,7 +27,6 @@ import { AmountArgShape, ChainAddressShape, ChainAmountShape, - CoinShape, DelegationShape, } from '../typeGuards.js'; import { @@ -35,40 +34,47 @@ import { maxClockSkew, tryDecodeResponse, } from '../utils/cosmos.js'; +import { orchestrationAccountMethods } from '../utils/orchestrationAccount.js'; import { dateInSeconds } from '../utils/time.js'; /** - * @import {AmountArg, IcaAccount, ChainAddress, CosmosValidatorAddress, ICQConnection, StakingAccountActions, DenomAmount} from '../types.js'; + * @import {AmountArg, IcaAccount, ChainAddress, CosmosValidatorAddress, ICQConnection, StakingAccountActions, DenomAmount, OrchestrationAccountI, DenomArg} from '../types.js'; * @import {RecorderKit, MakeRecorderKit} from '@agoric/zoe/src/contractSupport/recorder.js'; * @import {Coin} from '@agoric/cosmic-proto/cosmos/base/v1beta1/coin.js'; * @import {Delegation} from '@agoric/cosmic-proto/cosmos/staking/v1beta1/staking.js'; + * @import {Remote} from '@agoric/internal'; * @import {TimerService} from '@agoric/time'; * @import {Zone} from '@agoric/zone'; */ -const trace = makeTracer('StakingAccountHolder'); +const trace = makeTracer('ComosOrchestrationAccountHolder'); const { Fail } = assert; /** - * @typedef {object} StakingAccountNotification + * @typedef {object} ComosOrchestrationAccountNotification * @property {ChainAddress} chainAddress */ /** * @typedef {{ - * topicKit: RecorderKit; + * topicKit: RecorderKit; * account: IcaAccount; * chainAddress: ChainAddress; * icqConnection: ICQConnection; * bondDenom: string; - * timer: TimerService; + * timer: Remote; * }} State */ +/** @see {OrchestrationAccountI} */ export const IcaAccountHolderI = M.interface('IcaAccountHolder', { + ...orchestrationAccountMethods, + asContinuingOffer: M.call().returns({ + publicSubscribers: M.any(), + invitationMakers: M.any(), + holder: M.any(), + }), getPublicTopics: M.call().returns(TopicsRecordShape), - getAddress: M.call().returns(ChainAddressShape), - getBalance: M.callWhen().optional(M.string()).returns(CoinShape), delegate: M.callWhen(ChainAddressShape, AmountShape).returns(M.undefined()), redelegate: M.callWhen( ChainAddressShape, @@ -106,8 +112,12 @@ const toDenomAmount = c => ({ denom: c.denom, value: BigInt(c.amount) }); * @param {MakeRecorderKit} makeRecorderKit * @param {ZCF} zcf */ -export const prepareStakingAccountKit = (zone, makeRecorderKit, zcf) => { - const makeStakingAccountKit = zone.exoClassKit( +export const prepareCosmosOrchestrationAccountKit = ( + zone, + makeRecorderKit, + zcf, +) => { + const makeCosmosOrchestrationAccountKit = zone.exoClassKit( 'Staking Account Holder', { helper: M.interface('helper', { @@ -138,9 +148,9 @@ export const prepareStakingAccountKit = (zone, makeRecorderKit, zcf) => { * @param {string} bondDenom e.g. 'uatom' * @param {object} io * @param {IcaAccount} io.account - * @param {StorageNode} io.storageNode + * @param {Remote} io.storageNode * @param {ICQConnection} io.icqConnection - * @param {TimerService} io.timer + * @param {Remote} io.timer * @returns {State} */ (chainAddress, bondDenom, io) => { @@ -221,9 +231,7 @@ export const prepareStakingAccountKit = (zone, makeRecorderKit, zcf) => { return this.facets.holder.withdrawReward(validator); }, 'WithdrawReward'); }, - /** - * @param {Delegation[]} delegations - */ + /** @param {Delegation[]} delegations */ Undelegate(delegations) { trace('Undelegate', delegations); @@ -245,6 +253,14 @@ export const prepareStakingAccountKit = (zone, makeRecorderKit, zcf) => { }, }, holder: { + asContinuingOffer() { + const { holder, invitationMakers } = this.facets; + return harden({ + publicSubscribers: holder.getPublicTopics(), + invitationMakers, + holder, + }); + }, getPublicTopics() { const { topicKit } = this.state; return harden({ @@ -285,6 +301,15 @@ export const prepareStakingAccountKit = (zone, makeRecorderKit, zcf) => { expect(result, trivialDelegateResponse, 'MsgDelegateResponse'); }, + async deposit(payment) { + trace('deposit', payment); + console.error( + 'FIXME deposit noop until https://github.com/Agoric/agoric-sdk/issues/9193', + ); + }, + async getBalances() { + throw Error('not yet implemented'); + }, /** * _Assumes users has already sent funds to their ICA, until #9193 * @@ -333,12 +358,12 @@ export const prepareStakingAccountKit = (zone, makeRecorderKit, zcf) => { return harden(coins.map(toDenomAmount)); }, /** - * @param {DenomAmount['denom']} [denom] - defaults to bondDenom + * @param {DenomArg} denom * @returns {Promise} */ async getBalance(denom) { - const { chainAddress, icqConnection, bondDenom } = this.state; - denom ||= bondDenom; + const { chainAddress, icqConnection } = this.state; + // TODO #9211 lookup denom from brand assert.typeof(denom, 'string'); const [result] = await E(icqConnection).query([ @@ -357,13 +382,26 @@ export const prepareStakingAccountKit = (zone, makeRecorderKit, zcf) => { return harden(toDenomAmount(balance)); }, + send(toAccount, amount) { + console.log('send got', toAccount, amount); + throw Error('not yet implemented'); + }, + + transfer(amount, msg) { + console.log('transferSteps got', amount, msg); + throw Error('not yet implemented'); + }, + + transferSteps(amount, msg) { + console.log('transferSteps got', amount, msg); + throw Error('not yet implemented'); + }, + withdrawRewards() { throw assert.error('Not implemented'); }, - /** - * @param {Delegation[]} delegations - */ + /** @param {Delegation[]} delegations */ async undelegate(delegations) { trace('undelegate', delegations); const { helper } = this.facets; @@ -394,18 +432,35 @@ export const prepareStakingAccountKit = (zone, makeRecorderKit, zcf) => { }, ); - /** check holder facet against StakingAccountActions interface. */ - // eslint-disable-next-line no-unused-vars - const typeCheck = () => { - /** @type {any} */ - const arg = null; - /** @satisfies {StakingAccountActions} */ - // eslint-disable-next-line no-unused-vars - const kit = makeStakingAccountKit(arg, arg, arg).holder; - }; - - return makeStakingAccountKit; + return makeCosmosOrchestrationAccountKit; }; -/** @typedef {ReturnType>} StakingAccountKit */ -/** @typedef {StakingAccountKit['holder']} StakingAccounHolder */ +/** + * @typedef {ReturnType< + * ReturnType + * >} CosmosOrchestrationAccountKit + */ + +/** + * @param {Zone} zone + * @param {MakeRecorderKit} makeRecorderKit + * @param {ZCF} zcf + * @returns {( + * ...args: Parameters< + * ReturnType + * > + * ) => CosmosOrchestrationAccountKit['holder']} + */ +export const prepareCosmosOrchestrationAccount = ( + zone, + makeRecorderKit, + zcf, +) => { + const makeKit = prepareCosmosOrchestrationAccountKit( + zone, + makeRecorderKit, + zcf, + ); + return (...args) => makeKit(...args).holder; +}; +/** @typedef {CosmosOrchestrationAccountKit['holder']} CosmosOrchestrationAccount */ diff --git a/packages/orchestration/src/exos/icqConnectionKit.js b/packages/orchestration/src/exos/icqConnectionKit.js index 8c9c8323f2d..6c29d491ce3 100644 --- a/packages/orchestration/src/exos/icqConnectionKit.js +++ b/packages/orchestration/src/exos/icqConnectionKit.js @@ -61,14 +61,12 @@ export const prepareICQConnectionKit = zone => * @param {Port} port */ port => - /** @type {ICQConnectionKitState} */ ( - harden({ - port, - connection: undefined, - remoteAddress: undefined, - localAddress: undefined, - }) - ), + /** @type {ICQConnectionKitState} */ ({ + port, + connection: undefined, + remoteAddress: undefined, + localAddress: undefined, + }), { connection: { getLocalAddress() { diff --git a/packages/orchestration/src/exos/local-chain-account-kit.js b/packages/orchestration/src/exos/local-chain-account-kit.js index 0571c1bbc79..655c843423c 100644 --- a/packages/orchestration/src/exos/local-chain-account-kit.js +++ b/packages/orchestration/src/exos/local-chain-account-kit.js @@ -243,10 +243,12 @@ export const prepareLocalChainAccountKit = ( // TODO #9211 lookup denom from brand if ('brand' in amount) throw Fail`ERTP Amounts not yet supported`; + destination.chainId in agoricChainInfo.connections || + Fail`Unknown chain ${destination.chainId}`; + // TODO #8879 chainInfo and #9063 well-known chains - const { transferChannel } = agoricChainInfo.connections.get( - destination.chainId, - ); + const { transferChannel } = + agoricChainInfo.connections[destination.chainId]; await null; // set a `timeoutTimestamp` if caller does not supply either `timeoutHeight` or `timeoutTimestamp` diff --git a/packages/orchestration/src/facade.js b/packages/orchestration/src/facade.js index 04cd6e68e50..33fb0107e33 100644 --- a/packages/orchestration/src/facade.js +++ b/packages/orchestration/src/facade.js @@ -1,6 +1,7 @@ /** @file Orchestration service */ import { E } from '@endo/far'; +import { prepareCosmosOrchestrationAccount } from './exos/cosmosOrchestrationAccount.js'; /** * @import {Zone} from '@agoric/zone'; @@ -8,12 +9,25 @@ import { E } from '@endo/far'; * @import {LocalChain} from '@agoric/vats/src/localchain.js'; * @import {Remote} from '@agoric/internal'; * @import {OrchestrationService} from './service.js'; - * @import {Chain, ChainInfo, OrchestrationAccount, Orchestrator} from './types.js'; + * @import {Chain, ChainInfo, CosmosChainInfo, KnownChains, OrchestrationAccount, Orchestrator} from './types.js'; */ /** @type {any} */ const anyVal = null; +// FIXME should be configurable +const mockLocalChainInfo = { + allegedName: 'agoric', + allowedMessages: [], + allowedQueries: [], + chainId: 'agoriclocal', + connections: anyVal, + ibcHooksEnabled: true, + icaEnabled: true, + icqEnabled: true, + pfmEnabled: true, +}; + /** * @param {Remote} localchain * @returns {Chain} @@ -22,18 +36,10 @@ const makeLocalChainFacade = localchain => { return { /** @returns {Promise} */ async getChainInfo() { - return { - allegedName: 'agoric', - allowedMessages: [], - allowedQueries: [], - chainId: 'agoric-3', - connections: anyVal, - ibcHooksEnabled: true, - icaEnabled: true, - icqEnabled: true, - pfmEnabled: true, - }; + return mockLocalChainInfo; }, + + // @ts-expect-error FIXME promise resolution through membrane async makeAccount() { const account = await E(localchain).makeAccount(); @@ -42,6 +48,30 @@ const makeLocalChainFacade = localchain => { console.log('deposit got', payment); return E(account).deposit(payment); }, + async getAddress() { + const addressStr = await E(account).getAddress(); + return { + address: addressStr, + chainId: mockLocalChainInfo.chainId, + addressEncoding: 'bech32', + }; + }, + getBalance(_denom) { + // FIXME map denom to Brand + const brand = /** @type {any} */ (null); + return E(account).getBalance(brand); + }, + getBalances() { + throw new Error('not yet implemented'); + }, + send(toAccount, amount) { + // FIXME implement + console.log('send got', toAccount, amount); + }, + transfer(amount, destination, opts) { + // FIXME implement + console.log('transfer got', amount, destination, opts); + }, transferSteps(amount, msg) { console.log('transferSteps got', amount, msg); return Promise.resolve(); @@ -54,78 +84,57 @@ const makeLocalChainFacade = localchain => { /** * @template {string} C * @param {C} name - * @returns {Chain} + * @param {object} io + * @param {Remote} io.orchestration + * @param {Remote} io.timer + * @param {ZCF} io.zcf + * @param {Zone} io.zone + * @returns {Chain} */ -const makeRemoteChainFacade = name => { - const chainInfo = { +const makeRemoteChainFacade = (name, { orchestration, timer, zcf, zone }) => { + const chainInfo = /** @type {CosmosChainInfo} */ ({ allegedName: name, chainId: 'fixme', - }; + connections: {}, + icaEnabled: true, + icqEnabled: true, + pfmEnabled: true, + ibcHooksEnabled: true, + allowedMessages: [], + allowedQueries: [], + }); + const makeRecorderKit = () => anyVal; + const makeCosmosOrchestrationAccount = prepareCosmosOrchestrationAccount( + zone.subZone(name), + makeRecorderKit, + zcf, + ); + return { - /** @returns {Promise} */ - getChainInfo: async () => anyVal, + getChainInfo: async () => chainInfo, /** @returns {Promise>} */ makeAccount: async () => { console.log('makeAccount for', name); - // @ts-expect-error fake yet - return { - delegate(validator, amount) { - console.log('delegate got', validator, amount); - return Promise.resolve(); - }, - deposit(payment) { - console.log('deposit got', payment); - return Promise.resolve(); - }, - getAddress() { - return { - chainId: chainInfo.chainId, - address: 'an address!', - addressEncoding: 'bech32', - }; - }, - getBalance(_denom) { - console.error('getBalance not yet implemented'); - return Promise.resolve({ denom: 'fixme', value: 0n }); - }, - getBalances() { - throw new Error('not yet implemented'); - }, - getDelegations() { - console.error('getDelegations not yet implemented'); - return []; - }, - getRedelegations() { - throw new Error('not yet implemented'); - }, - getUnbondingDelegations() { - throw new Error('not yet implemented'); - }, - liquidStake() { - console.error('liquidStake not yet implemented'); - return 0n; - }, - send(toAccount, amount) { - console.log('send got', toAccount, amount); - return Promise.resolve(); - }, - transfer(amount, destination, memo) { - console.log('transfer got', amount, destination, memo); - return Promise.resolve(); - }, - transferSteps(amount, msg) { - console.log('transferSteps got', amount, msg); - return Promise.resolve(); - }, - undelegate(validator, amount) { - console.log('undelegate got', validator, amount); - return Promise.resolve(); - }, - withdraw(amount) { - console.log('withdraw got', amount); - return Promise.resolve(); - }, - }; + + // FIXME look up real values + const hostConnectionId = 'connection-1'; + const controllerConnectionId = 'connection-2'; + + const icaAccount = await E(orchestration).makeAccount( + hostConnectionId, + controllerConnectionId, + ); + + const address = await E(icaAccount).getAddress(); + + // FIXME look up real values + const bondDenom = name; + return makeCosmosOrchestrationAccount(address, bondDenom, { + account: icaAccount, + storageNode: anyVal, + icqConnection: anyVal, + timer, + }); }, }; }; @@ -133,10 +142,10 @@ const makeRemoteChainFacade = name => { /** * @param {{ * zone: Zone; - * timerService: Remote | null; + * timerService: Remote; * zcf: ZCF; * storageNode: Remote; - * orchestrationService: Remote | null; + * orchestrationService: Remote; * localchain: Remote; * }} powers */ @@ -172,7 +181,13 @@ export const makeOrchestrationFacade = ({ if (name === 'agoric') { return makeLocalChainFacade(localchain); } - return makeRemoteChainFacade(name); + + return makeRemoteChainFacade(name, { + orchestration: orchestrationService, + timer: timerService, + zcf, + zone, + }); }, makeLocalAccount() { return E(localchain).makeAccount(); diff --git a/packages/orchestration/src/orchestration-api.ts b/packages/orchestration/src/orchestration-api.ts index 3ee02806bb0..0f022b3c336 100644 --- a/packages/orchestration/src/orchestration-api.ts +++ b/packages/orchestration/src/orchestration-api.ts @@ -170,7 +170,7 @@ export interface OrchestrationAccountI { * deposit payment from zoe to the account. For remote accounts, * an IBC Transfer will be executed to transfer funds there. */ - deposit: (payment: Payment) => Promise; + deposit: (payment: Payment<'nat'>) => Promise; } /** diff --git a/packages/orchestration/src/utils/mockChainInfo.js b/packages/orchestration/src/utils/mockChainInfo.js index 136306c4e54..d7fb12cea0e 100644 --- a/packages/orchestration/src/utils/mockChainInfo.js +++ b/packages/orchestration/src/utils/mockChainInfo.js @@ -68,19 +68,11 @@ const connectionEntries = harden({ }); /** - * @param {Zone} zone * @returns {Pick} */ -export const prepareMockChainInfo = zone => { - const agoricConnections = - /** @type {import('@agoric/store').MapStore} */ ( - zone.mapStore('ibcConnections') - ); - - agoricConnections.addAll(Object.entries(connectionEntries)); - +export const prepareMockChainInfo = () => { return harden({ chainId: 'agoriclocal', - connections: agoricConnections, + connections: connectionEntries, }); }; diff --git a/packages/orchestration/src/utils/orchestrationAccount.js b/packages/orchestration/src/utils/orchestrationAccount.js new file mode 100644 index 00000000000..b5b325980c1 --- /dev/null +++ b/packages/orchestration/src/utils/orchestrationAccount.js @@ -0,0 +1,19 @@ +import { M } from '@endo/patterns'; +import { PaymentShape } from '@agoric/ertp'; +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(), + ), + transferSteps: M.callWhen(AmountArgShape, M.any()).returns(M.undefined()), + deposit: M.callWhen(PaymentShape).returns(M.undefined()), +}; diff --git a/packages/orchestration/test/examples/stake-atom.contract.test.ts b/packages/orchestration/test/examples/stake-atom.contract.test.ts new file mode 100644 index 00000000000..652997171c4 --- /dev/null +++ b/packages/orchestration/test/examples/stake-atom.contract.test.ts @@ -0,0 +1,67 @@ +import { test } from '@agoric/zoe/tools/prepare-test-env-ava.js'; + +import { AmountMath } from '@agoric/ertp'; +import { setUpZoeForTest } from '@agoric/zoe/tools/setup-zoe.js'; +import { E } from '@endo/far'; +import path from 'path'; +import type { Installation } from '@agoric/zoe/src/zoeService/utils.js'; +import { commonSetup } from '../supports.js'; + +const dirname = path.dirname(new URL(import.meta.url).pathname); + +const contractFile = `${dirname}/../../src/examples/stakeAtom.contract.js`; +type StartFn = + typeof import('@agoric/orchestration/src/examples/stakeAtom.contract.js').start; + +const startContract = async ({ + orchestration, + timer, + marshaller, + storage, + bld, +}) => { + const { zoe, bundleAndInstall } = await setUpZoeForTest(); + const installation: Installation = + await bundleAndInstall(contractFile); + + const { publicFacet } = await E(zoe).startInstance( + installation, + { In: bld.issuer }, + { + hostConnectionId: 'connection-1', + controllerConnectionId: 'connection-2', + bondDenom: 'uatom', + }, + { + marshaller, + orchestration, + storageNode: storage.rootNode, + timer, + }, + ); + return { publicFacet, zoe }; +}; + +test('makeAccount, deposit, withdraw', async t => { + const { + bootstrap, + brands: { ist }, + utils, + } = await commonSetup(t); + const { publicFacet } = await startContract({ ...bootstrap, bld: ist }); + + t.log('make an ICA account'); + const account = await E(publicFacet).makeAccount(); + t.truthy(account, 'account is returned'); + const address = await E(account).getAddress(); + // XXX address.address is weird + // t.regex(address.address, /agoric1/); + t.like(address, { chainId: 'FIXME', addressEncoding: 'bech32' }); + + t.log('deposit 100 bld to account'); + await E(account).deposit(await utils.pourPayment(ist.units(100))); + + await t.throwsAsync(E(account).getBalances(), { + message: 'not yet implemented', + }); +}); diff --git a/packages/orchestration/test/examples/stake-bld.contract.test.ts b/packages/orchestration/test/examples/stake-bld.contract.test.ts index 412c60ab876..7dc11530e48 100644 --- a/packages/orchestration/test/examples/stake-bld.contract.test.ts +++ b/packages/orchestration/test/examples/stake-bld.contract.test.ts @@ -12,8 +12,13 @@ const contractFile = `${dirname}/../../src/examples/stakeBld.contract.js`; type StartFn = typeof import('@agoric/orchestration/src/examples/stakeBld.contract.js').start; -const coreEval = async (t, { timer, localchain, marshaller, storage, bld }) => { - t.log('install stakeBld contract'); +const startContract = async ({ + timer, + localchain, + marshaller, + storage, + bld, +}) => { const { zoe, bundleAndInstall } = await setUpZoeForTest(); const installation: Installation = await bundleAndInstall(contractFile); @@ -39,7 +44,7 @@ test('makeAccount, deposit, withdraw', async t => { brands: { bld }, utils, } = await commonSetup(t); - const { publicFacet } = await coreEval(t, { ...bootstrap, bld }); + const { publicFacet } = await startContract({ ...bootstrap, bld }); t.log('make a LocalChainAccount'); const account = await E(publicFacet).makeAccount(); @@ -72,7 +77,7 @@ test('makeStakeBldInvitation', async t => { brands: { bld }, utils, } = await commonSetup(t); - const { publicFacet, zoe } = await coreEval(t, { ...bootstrap, bld }); + const { publicFacet, zoe } = await startContract({ ...bootstrap, bld }); t.log('call makeStakeBldInvitation'); const inv = await E(publicFacet).makeStakeBldInvitation(); @@ -113,7 +118,7 @@ test('makeAccountInvitationMaker', async t => { bootstrap, brands: { bld }, } = await commonSetup(t); - const { publicFacet, zoe } = await coreEval(t, { ...bootstrap, bld }); + const { publicFacet, zoe } = await startContract({ ...bootstrap, bld }); const inv = await E(publicFacet).makeAccountInvitationMaker(); diff --git a/packages/orchestration/test/examples/swapExample.test.ts b/packages/orchestration/test/examples/swapExample.test.ts index f973a47f733..ca1a8852fe5 100644 --- a/packages/orchestration/test/examples/swapExample.test.ts +++ b/packages/orchestration/test/examples/swapExample.test.ts @@ -29,10 +29,9 @@ test('start', async t => { {}, { localchain: bootstrap.localchain, - orchestrationService: null as any, + orchestrationService: bootstrap.orchestration, storageNode: bootstrap.storage.rootNode, timerService: bootstrap.timer, - zone: bootstrap.rootZone, }, ); diff --git a/packages/orchestration/test/examples/unbondExample.test.ts b/packages/orchestration/test/examples/unbondExample.test.ts index 06cf5f9f0d8..b78e606fdf0 100644 --- a/packages/orchestration/test/examples/unbondExample.test.ts +++ b/packages/orchestration/test/examples/unbondExample.test.ts @@ -27,7 +27,7 @@ test('start', async t => { {}, { localchain: bootstrap.localchain, - orchestrationService: null as any, + orchestrationService: bootstrap.orchestration, storageNode: bootstrap.storage.rootNode, timerService: bootstrap.timer, }, diff --git a/packages/orchestration/test/exos/local-chain-account-kit.test.ts b/packages/orchestration/test/exos/local-chain-account-kit.test.ts index 0147054f5b1..368b1d5489a 100644 --- a/packages/orchestration/test/exos/local-chain-account-kit.test.ts +++ b/packages/orchestration/test/exos/local-chain-account-kit.test.ts @@ -17,7 +17,7 @@ test('deposit, withdraw', async t => { const { timer, localchain, marshaller, rootZone, storage } = bootstrap; t.log('chainInfo mocked via `prepareMockChainInfo` until #8879'); - const agoricChainInfo = prepareMockChainInfo(rootZone.subZone('chainInfo')); + const agoricChainInfo = prepareMockChainInfo(); t.log('exo setup - prepareLocalChainAccountKit'); const { makeRecorderKit } = prepareRecorderKitMakers( @@ -85,7 +85,7 @@ test('delegate, undelegate', async t => { const { timer, localchain, marshaller, rootZone, storage } = bootstrap; t.log('chainInfo mocked via `prepareMockChainInfo` until #8879'); - const agoricChainInfo = prepareMockChainInfo(rootZone.subZone('chainInfo')); + const agoricChainInfo = prepareMockChainInfo(); t.log('exo setup - prepareLocalChainAccountKit'); const { makeRecorderKit } = prepareRecorderKitMakers( @@ -136,7 +136,7 @@ test('transfer', async t => { const { timer, localchain, marshaller, rootZone, storage } = bootstrap; t.log('chainInfo mocked via `prepareMockChainInfo` until #8879'); - const agoricChainInfo = prepareMockChainInfo(rootZone.subZone('chainInfo')); + const agoricChainInfo = prepareMockChainInfo(); t.log('exo setup - prepareLocalChainAccountKit'); const { makeRecorderKit } = prepareRecorderKitMakers( @@ -207,7 +207,7 @@ test('transfer', async t => { await t.throwsAsync( () => E(account).transfer({ denom: 'ubld', value: 1n }, unknownDestination), { - message: /not found(.*)fakenet/, + message: /Unknown chain "fakenet"/, }, 'cannot create transfer msg with unknown chainId', ); diff --git a/packages/orchestration/test/network-fakes.ts b/packages/orchestration/test/network-fakes.ts new file mode 100644 index 00000000000..92a2f77ac68 --- /dev/null +++ b/packages/orchestration/test/network-fakes.ts @@ -0,0 +1,117 @@ +// @ts-check +import { prepareVowTools } from '@agoric/vow'; +import assert from 'node:assert/strict'; +import { + prepareEchoConnectionKit, + prepareNetworkProtocol, + preparePortAllocator, + type ListenHandler, + type MakeEchoConnectionKit, +} from '@agoric/network'; +import type { Zone } from '@agoric/zone'; + +// eslint-disable-next-line no-constant-condition +const log = false ? console.log : () => {}; + +export const prepareProtocolHandler = ( + zone: Zone, + makeEchoConnectionHandler: MakeEchoConnectionKit, + { when }, +) => { + const makeProtocolHandler = zone.exoClass( + 'ProtocolHandler', + undefined, + () => { + return { + l: undefined as ListenHandler | undefined, + lp: undefined, + nonce: 0, + }; + }, + { + async onInstantiate(_port, _localAddr, _remote, _protocol) { + return ''; + }, + async onCreate(_protocol, _impl) { + log('created', _protocol, _impl); + }, + async generatePortID() { + this.state.nonce += 1; + return `port-${this.state.nonce}`; + }, + async onBind(port, localAddr) { + assert(port, `port is supplied to onBind`); + assert(localAddr, `local address is supplied to onBind`); + }, + async onConnect(port, localAddr, remoteAddr) { + assert(port, `port is tracked in onConnect`); + assert(localAddr, `local address is supplied to onConnect`); + assert(remoteAddr, `remote address is supplied to onConnect`); + if (!this.state.lp) { + return { handler: makeEchoConnectionHandler().handler }; + } + assert(this.state.l); + const ch = await when( + this.state.l.onAccept( + this.state.lp, + localAddr, + remoteAddr, + this.state.l, + ), + ); + return { localAddr, handler: ch }; + }, + async onListen(port, localAddr, listenHandler) { + assert(port, `port is tracked in onListen`); + assert(localAddr, `local address is supplied to onListen`); + assert(listenHandler, `listen handler is tracked in onListen`); + this.state.lp = port; + this.state.l = listenHandler; + log('listening', port.getLocalAddress(), listenHandler); + }, + async onListenRemove(port, localAddr, listenHandler) { + assert(port, `port is tracked in onListen`); + assert(localAddr, `local address is supplied to onListen`); + assert.equal( + listenHandler, + this.state.lp && this.state.l, + `listenHandler is tracked in onListenRemove`, + ); + this.state.lp = undefined; + log('port done listening', port.getLocalAddress()); + }, + async onRevoke(port, localAddr) { + assert(port, `port is tracked in onRevoke`); + assert(localAddr, `local address is supplied to onRevoke`); + log('port done revoking', port.getLocalAddress()); + }, + }, + ); + + return makeProtocolHandler; +}; + +export const fakeNetworkEchoStuff = (zone: Zone) => { + const powers = prepareVowTools(zone); + const { makeVowKit, when } = powers; + + const makeNetworkProtocol = prepareNetworkProtocol(zone, powers); + const makeEchoConnectionHandler = prepareEchoConnectionKit(zone); + const makeProtocolHandler = prepareProtocolHandler( + zone, + makeEchoConnectionHandler, + powers, + ); + const protocol = makeNetworkProtocol(makeProtocolHandler()); + + const makePortAllocator = preparePortAllocator(zone, powers); + const portAllocator = makePortAllocator({ protocol }); + + return { + makeEchoConnectionHandler, + makeVowKit, + portAllocator, + protocol, + when, + }; +}; diff --git a/packages/orchestration/test/staking-ops.test.ts b/packages/orchestration/test/staking-ops.test.ts index c0bba8c3001..6daf8722874 100644 --- a/packages/orchestration/test/staking-ops.test.ts +++ b/packages/orchestration/test/staking-ops.test.ts @@ -17,9 +17,9 @@ import type { TimestampRecord, TimestampValue } from '@agoric/time'; import type { AnyJson } from '@agoric/cosmic-proto'; import { makeDurableZone } from '@agoric/zone/durable.js'; import { - prepareStakingAccountKit, + prepareCosmosOrchestrationAccountKit, trivialDelegateResponse, -} from '../src/exos/stakingAccountKit.js'; +} from '../src/exos/cosmosOrchestrationAccount.js'; import { encodeTxResponse } from '../src/utils/cosmos.js'; import type { IcaAccount, ChainAddress, ICQConnection } from '../src/types.js'; @@ -208,7 +208,7 @@ test('withdrawRewards() on StakingAccountHolder formats message correctly', asyn const s = makeScenario(); const { account, calls, timer } = s; const { makeRecorderKit, storageNode, zcf, icqConnection, zone } = s; - const make = prepareStakingAccountKit(zone, makeRecorderKit, zcf); + const make = prepareCosmosOrchestrationAccountKit(zone, makeRecorderKit, zcf); // Higher fidelity tests below use invitationMakers. const { holder } = make(account.getAddress(), 'uatom', { @@ -232,7 +232,11 @@ test(`delegate; redelegate using invitationMakers`, async t => { const { account, calls, timer } = s; const { makeRecorderKit, storageNode, zcf, zoe, icqConnection, zone } = s; const aBrand = Far('Token') as Brand<'nat'>; - const makeAccountKit = prepareStakingAccountKit(zone, makeRecorderKit, zcf); + const makeAccountKit = prepareCosmosOrchestrationAccountKit( + zone, + makeRecorderKit, + zcf, + ); const { invitationMakers } = makeAccountKit(account.getAddress(), 'uatom', { account, @@ -297,7 +301,11 @@ test(`withdraw rewards using invitationMakers`, async t => { const s = makeScenario(); const { account, calls, timer } = s; const { makeRecorderKit, storageNode, zcf, zoe, icqConnection, zone } = s; - const makeAccountKit = prepareStakingAccountKit(zone, makeRecorderKit, zcf); + const makeAccountKit = prepareCosmosOrchestrationAccountKit( + zone, + makeRecorderKit, + zcf, + ); const { invitationMakers } = makeAccountKit(account.getAddress(), 'uatom', { account, @@ -323,7 +331,11 @@ test(`undelegate waits for unbonding period`, async t => { const s = makeScenario(); const { account, calls, timer } = s; const { makeRecorderKit, storageNode, zcf, zoe, icqConnection, zone } = s; - const makeAccountKit = prepareStakingAccountKit(zone, makeRecorderKit, zcf); + const makeAccountKit = prepareCosmosOrchestrationAccountKit( + zone, + makeRecorderKit, + zcf, + ); const { invitationMakers } = makeAccountKit(account.getAddress(), 'uatom', { account, diff --git a/packages/orchestration/test/supports.ts b/packages/orchestration/test/supports.ts index 906cbe22f10..d13338b260b 100644 --- a/packages/orchestration/test/supports.ts +++ b/packages/orchestration/test/supports.ts @@ -9,6 +9,8 @@ import { buildZoeManualTimer } from '@agoric/zoe/tools/manualTimer.js'; import { withAmountUtils } from '@agoric/zoe/tools/test-utils.js'; import { makeHeapZone } from '@agoric/zone'; import { E } from '@endo/far'; +import { fakeNetworkEchoStuff } from './network-fakes.js'; +import { prepareOrchestrationTools } from '../src/service.js'; export { makeFakeLocalchainBridge } from '@agoric/vats/tools/fake-bridge.js'; @@ -43,12 +45,21 @@ export const commonSetup = async t => { const storage = makeFakeStorageKit('mockChainStorageRoot', { sequence: false, }); + + const { makeOrchestrationKit } = prepareOrchestrationTools( + rootZone.subZone('orchestration'), + ); + + const { portAllocator } = fakeNetworkEchoStuff(rootZone.subZone('network')); + const { public: orchestration } = makeOrchestrationKit({ portAllocator }); + return { bootstrap: { bankManager, timer, localchain, marshaller, + orchestration, rootZone, storage, }, diff --git a/packages/orchestration/test/types.test-d.ts b/packages/orchestration/test/types.test-d.ts index 4a4e61c7747..37095f7538d 100644 --- a/packages/orchestration/test/types.test-d.ts +++ b/packages/orchestration/test/types.test-d.ts @@ -5,8 +5,15 @@ import { expectNotType, expectType } from 'tsd'; import { typedJson } from '@agoric/cosmic-proto'; import type { MsgDelegateResponse } from '@agoric/cosmic-proto/cosmos/staking/v1beta1/tx.js'; import type { QueryAllBalancesResponse } from '@agoric/cosmic-proto/cosmos/bank/v1beta1/query.js'; -import type { ChainAddress, CosmosValidatorAddress } from '../src/types.js'; +import type { + ChainAddress, + CosmosValidatorAddress, + StakingAccountActions, +} from '../src/types.js'; import type { LocalChainAccountKit } from '../src/exos/local-chain-account-kit.js'; +import { prepareCosmosOrchestrationAccount } from '../src/exos/cosmosOrchestrationAccount.js'; + +const anyVal = null as any; const validatorAddr = { chainId: 'agoric3', @@ -46,3 +53,17 @@ expectNotType(chainAddr); expectType(results[0]); expectType(results[1]); } + +// CosmosOrchestrationAccount interfaces +{ + const makeCosmosOrchestrationAccount = prepareCosmosOrchestrationAccount( + anyVal, + anyVal, + anyVal, + ); + makeCosmosOrchestrationAccount( + anyVal, + anyVal, + anyVal, + ) satisfies StakingAccountActions; +} diff --git a/packages/smart-wallet/src/offerWatcher.js b/packages/smart-wallet/src/offerWatcher.js index 283adb41547..3b2bc004f62 100644 --- a/packages/smart-wallet/src/offerWatcher.js +++ b/packages/smart-wallet/src/offerWatcher.js @@ -12,8 +12,12 @@ import { deeplyFulfilledObject, objectMap } from '@agoric/internal'; import { UNPUBLISHED_RESULT } from './offers.js'; -/** @import {OfferSpec} from "./offers.js" */ -/** @import {PromiseWatcher} from '@agoric/swingset-liveslots' */ +/** + * @import {OfferSpec} from "./offers.js"; + * @import {ContinuingOfferResult} from "./types.js"; + * @import {Passable} from '@endo/pass-style'; + * @import {PromiseWatcher} from '@agoric/swingset-liveslots'; + */ /** * @template {any} T @@ -22,7 +26,7 @@ import { UNPUBLISHED_RESULT } from './offers.js'; /** * @typedef {{ - * resultWatcher: OfferPromiseWatcher; + * resultWatcher: OfferPromiseWatcher; * numWantsWatcher: OfferPromiseWatcher; * paymentWatcher: OfferPromiseWatcher; * }} OutcomeWatchers @@ -156,7 +160,7 @@ export const prepareOfferWatcher = baggage => { ); }, - /** @param {unknown} result */ + /** @param {Passable | ContinuingOfferResult} result */ publishResult(result) { const { state, facets } = this; @@ -182,7 +186,6 @@ export const prepareOfferWatcher = baggage => { state.invitationAmount, // @ts-expect-error narrowed by passStyle result.invitationMakers, - // @ts-expect-error narrowed by passStyle result.publicSubscribers, ); } diff --git a/packages/smart-wallet/src/types.d.ts b/packages/smart-wallet/src/types.d.ts index 54ad2a8cb0d..4e780451590 100644 --- a/packages/smart-wallet/src/types.d.ts +++ b/packages/smart-wallet/src/types.d.ts @@ -29,6 +29,11 @@ export type InvitationMakers = Record< export type PublicSubscribers = Record>; +export interface ContinuingOfferResult { + invitationMakers: InvitationMakers; + publicSubscribers: PublicSubscribers; +} + export type Cell = { get: () => T; set(val: T): void;