From be14fa588523fe270970ec4a5617b66327ccb988 Mon Sep 17 00:00:00 2001 From: 0xPatrick Date: Wed, 1 May 2024 13:11:02 -0400 Subject: [PATCH 1/7] fix(orchestration): remove cancelToken logic from undelegate --- .../src/examples/unbondExample.contract.js | 4 +--- packages/orchestration/src/types.d.ts | 23 ++++++++----------- 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/packages/orchestration/src/examples/unbondExample.contract.js b/packages/orchestration/src/examples/unbondExample.contract.js index b190010bd0e..ac33aec4453 100644 --- a/packages/orchestration/src/examples/unbondExample.contract.js +++ b/packages/orchestration/src/examples/unbondExample.contract.js @@ -50,10 +50,8 @@ export const start = async (zcf, privateArgs) => { const celestiaAccount = await celestia.createAccount(); const delegations = await celestiaAccount.getDelegations(); - const [undelegation] = await celestiaAccount.undelegate(delegations); - // wait for the undelegations to be complete (may take weeks) - await undelegation.completion; + await celestiaAccount.undelegate(delegations); // ??? should this be synchronous? depends on how names are resolved. const stride = await orch.getChain('stride'); diff --git a/packages/orchestration/src/types.d.ts b/packages/orchestration/src/types.d.ts index bd3de46d26e..08f81b07cd5 100644 --- a/packages/orchestration/src/types.d.ts +++ b/packages/orchestration/src/types.d.ts @@ -1,4 +1,10 @@ -import type { Amount, Brand, Payment, Purse } from '@agoric/ertp/exported.js'; +import type { + Amount, + Brand, + Payment, + Purse, + RemotableBrand, +} from '@agoric/ertp/exported.js'; import type { Timestamp } from '@agoric/time'; import type { Invitation } from '@agoric/zoe/exported.js'; import type { Any } from '@agoric/cosmic-proto/google/protobuf/any'; @@ -255,16 +261,6 @@ export interface ChainAccount { prepareTransfer: () => Promise; } -export interface Undelegation { - cancel: () => Promise; - response: MsgUndelegateResponse; - /** - * Resolves when the undelegation is complete and the tokens are no longer bonded. - * Note it may take weeks. - */ - completion: Promise; -} - /** * An object that supports high-level operations for an account on a remote chain. */ @@ -365,9 +361,10 @@ export interface BaseOrchestrationAccount { /** * Undelegate multiple delegations (concurrently). To delegate independently, pass an array with one item. - * @param delegations - the delegation to undelegate + * Resolves when the undelegation is complete and the tokens are no longer bonded. Note it may take weeks. + * @param {Delegation[]} delegations - the delegation to undelegate */ - undelegate: (delegations: Delegation[]) => Promise; + undelegate: (delegations: Delegation[]) => Promise; /** * Withdraw rewards from all validators. The promise settles when the rewards are withdrawn. From 330ebeaa145cbc6fc48cdf7f72f1449b04f906de Mon Sep 17 00:00:00 2001 From: 0xPatrick Date: Wed, 1 May 2024 13:24:39 -0400 Subject: [PATCH 2/7] refactor(orchestration): rename createAccount -> makeAccount - to follow [Method Naming Structure](https://docs.agoric.com/guides/ertp/#method-naming-structure) --- .../test/bootstrapTests/test-orchestration.ts | 6 +++--- .../bootstrapTests/test-vat-orchestration.ts | 14 +++++++------- .../src/examples/stakeAtom.contract.js | 18 +++++++++--------- .../src/examples/swapExample.contract.js | 4 ++-- .../src/examples/unbondExample.contract.js | 4 ++-- .../src/exos/stakingAccountKit.js | 2 +- packages/orchestration/src/service.js | 4 ++-- packages/orchestration/src/types.d.ts | 2 +- 8 files changed, 27 insertions(+), 27 deletions(-) diff --git a/packages/boot/test/bootstrapTests/test-orchestration.ts b/packages/boot/test/bootstrapTests/test-orchestration.ts index 6c67392d683..f61088d1cc3 100644 --- a/packages/boot/test/bootstrapTests/test-orchestration.ts +++ b/packages/boot/test/bootstrapTests/test-orchestration.ts @@ -113,9 +113,9 @@ test.serial('stakeAtom - repl-style', async t => { const publicFacet = await EV(zoe).getPublicFacet(instance); t.truthy(publicFacet, 'stakeAtom publicFacet is available'); - const account = await EV(publicFacet).createAccount(); + const account = await EV(publicFacet).makeAccount(); t.log('account', account); - t.truthy(account, 'createAccount returns an account on ATOM connection'); + t.truthy(account, 'makeAccount returns an account on ATOM connection'); t.truthy( matches(account, M.remotable('ChainAccount')), 'account is a remotable', @@ -140,7 +140,7 @@ test.serial('stakeAtom - smart wallet', async t => { invitationSpec: { source: 'agoricContract', instancePath: ['stakeAtom'], - callPipe: [['makeCreateAccountInvitation']], + callPipe: [['makeAccountInvitation']], }, proposal: {}, }); diff --git a/packages/boot/test/bootstrapTests/test-vat-orchestration.ts b/packages/boot/test/bootstrapTests/test-vat-orchestration.ts index 5eae438d01a..63d37599fc5 100644 --- a/packages/boot/test/bootstrapTests/test-vat-orchestration.ts +++ b/packages/boot/test/bootstrapTests/test-vat-orchestration.ts @@ -60,18 +60,18 @@ test.before(async t => { test.after.always(t => t.context.shutdown?.()); -test('createAccount returns an ICA connection', async t => { +test('makeAccount returns an ICA connection', async t => { const { runUtils: { EV }, } = t.context; const orchestration = await EV.vat('bootstrap').consumeItem('orchestration'); - const account = await EV(orchestration).createAccount( + const account = await EV(orchestration).makeAccount( 'connection-0', 'connection-0', ); - t.truthy(account, 'createAccount returns an account'); + t.truthy(account, 'makeAccount returns an account'); t.truthy( matches(account, M.remotable('ChainAccount')), 'account is a remotable', @@ -103,11 +103,11 @@ test('ICA connection can be closed', async t => { const orchestration: OrchestrationService = await EV.vat('bootstrap').consumeItem('orchestration'); - const account = await EV(orchestration).createAccount( + const account = await EV(orchestration).makeAccount( 'connection-0', 'connection-0', ); - t.truthy(account, 'createAccount returns an account'); + t.truthy(account, 'makeAccount returns an account'); await EV(account).close(); @@ -123,11 +123,11 @@ test('ICA connection can send msg with proto3', async t => { const orchestration = await EV.vat('bootstrap').consumeItem('orchestration'); - const account: ChainAccount = await EV(orchestration).createAccount( + const account: ChainAccount = await EV(orchestration).makeAccount( 'connection-0', 'connection-0', ); - t.truthy(account, 'createAccount returns an account'); + t.truthy(account, 'makeAccount returns an account'); // @ts-expect-error intentional await t.throwsAsync(EV(account).executeEncodedTx('malformed'), { diff --git a/packages/orchestration/src/examples/stakeAtom.contract.js b/packages/orchestration/src/examples/stakeAtom.contract.js index 614061cde5b..6f16a8e6451 100644 --- a/packages/orchestration/src/examples/stakeAtom.contract.js +++ b/packages/orchestration/src/examples/stakeAtom.contract.js @@ -47,8 +47,8 @@ export const start = async (zcf, privateArgs, baggage) => { zcf, ); - async function createAccount() { - const account = await E(orchestration).createAccount( + async function makeAccount() { + const account = await E(orchestration).makeAccount( hostConnectionId, controllerConnectionId, ); @@ -69,20 +69,20 @@ export const start = async (zcf, privateArgs, baggage) => { const publicFacet = zone.exo( 'StakeAtom', M.interface('StakeAtomI', { - createAccount: M.callWhen().returns(M.remotable('ChainAccount')), - makeCreateAccountInvitation: M.call().returns(M.promise()), + makeAccount: M.callWhen().returns(M.remotable('ChainAccount')), + makeAccountInvitation: M.call().returns(M.promise()), }), { - async createAccount() { - trace('createAccount'); - return createAccount().then(({ account }) => account); + async makeAccount() { + trace('makeAccount'); + return makeAccount().then(({ account }) => account); }, - makeCreateAccountInvitation() { + makeAccountInvitation() { trace('makeCreateAccountInvitation'); return zcf.makeInvitation( async seat => { seat.exit(); - return createAccount(); + return makeAccount(); }, 'wantStakingAccount', undefined, diff --git a/packages/orchestration/src/examples/swapExample.contract.js b/packages/orchestration/src/examples/swapExample.contract.js index 1694f9568b7..306abf8ebc7 100644 --- a/packages/orchestration/src/examples/swapExample.contract.js +++ b/packages/orchestration/src/examples/swapExample.contract.js @@ -48,8 +48,8 @@ export const start = async (zcf, privateArgs) => { const agoric = await orch.getChain('agoric'); const [celestiaAccount, localAccount] = await Promise.all([ - celestia.createAccount(), - agoric.createAccount(), + celestia.makeAccount(), + agoric.makeAccount(), ]); const tiaAddress = await celestiaAccount.getChainAddress(); diff --git a/packages/orchestration/src/examples/unbondExample.contract.js b/packages/orchestration/src/examples/unbondExample.contract.js index ac33aec4453..664d77ea650 100644 --- a/packages/orchestration/src/examples/unbondExample.contract.js +++ b/packages/orchestration/src/examples/unbondExample.contract.js @@ -47,7 +47,7 @@ export const start = async (zcf, privateArgs) => { // ??? could these be passed in? It would reduce the size of this handler, // keeping it focused on long-running operations. const celestia = await orch.getChain('celestia'); - const celestiaAccount = await celestia.createAccount(); + const celestiaAccount = await celestia.makeAccount(); const delegations = await celestiaAccount.getDelegations(); // wait for the undelegations to be complete (may take weeks) @@ -55,7 +55,7 @@ export const start = async (zcf, privateArgs) => { // ??? should this be synchronous? depends on how names are resolved. const stride = await orch.getChain('stride'); - const strideAccount = await stride.createAccount(); + const strideAccount = await stride.makeAccount(); // TODO the `TIA` string actually needs to be the Brand from AgoricNames const tiaAmt = await celestiaAccount.getBalance('TIA'); diff --git a/packages/orchestration/src/exos/stakingAccountKit.js b/packages/orchestration/src/exos/stakingAccountKit.js index 5c6f003475a..446a586bff2 100644 --- a/packages/orchestration/src/exos/stakingAccountKit.js +++ b/packages/orchestration/src/exos/stakingAccountKit.js @@ -94,7 +94,7 @@ export const prepareStakingAccountKit = (baggage, makeRecorderKit, zcf) => { return this.state.topicKit.recorder; }, // TODO move this beneath the Orchestration abstraction, - // to the OrchestrationAccount provided by createAccount() + // to the OrchestrationAccount provided by makeAccount() /** * _Assumes users has already sent funds to their ICA, until #9193 * @param {string} validatorAddress diff --git a/packages/orchestration/src/service.js b/packages/orchestration/src/service.js index a4443dd665d..db0d2ddaee7 100644 --- a/packages/orchestration/src/service.js +++ b/packages/orchestration/src/service.js @@ -210,7 +210,7 @@ const prepareChainAccount = zone => ); export const OrchestrationI = M.interface('Orchestration', { - createAccount: M.callWhen(M.string(), M.string()).returns( + makeAccount: M.callWhen(M.string(), M.string()).returns( M.remotable('ChainAccount'), ), }); @@ -254,7 +254,7 @@ const prepareOrchestration = (zone, createChainAccount) => * self connection_id * @returns {Promise} */ - async createAccount(hostConnectionId, controllerConnectionId) { + async makeAccount(hostConnectionId, controllerConnectionId) { const port = await this.facets.self.bindPort(); const remoteConnAddr = makeICAConnectionAddress( diff --git a/packages/orchestration/src/types.d.ts b/packages/orchestration/src/types.d.ts index 08f81b07cd5..09d90834c15 100644 --- a/packages/orchestration/src/types.d.ts +++ b/packages/orchestration/src/types.d.ts @@ -209,7 +209,7 @@ export interface Chain { * Creates a new account on the remote chain. * @returns an object that controls a new remote account on Chain */ - createAccount: () => Promise>; + makeAccount: () => Promise>; // FUTURE supply optional port object; also fetch port object /** From f951cde10ee6618660938b2e5b404f797231d8e2 Mon Sep 17 00:00:00 2001 From: 0xPatrick Date: Wed, 1 May 2024 16:06:29 -0400 Subject: [PATCH 3/7] fix(orchestration): rename `.getAccountAddress` -> `.getAddress` --- .../bootstrapTests/test-vat-orchestration.ts | 2 +- .../src/examples/stakeAtom.contract.js | 6 +++--- .../src/examples/swapExample.contract.js | 2 +- .../src/examples/unbondExample.contract.js | 2 +- .../src/exos/stakingAccountKit.js | 19 ++++++++++--------- packages/orchestration/src/service.js | 16 ++++++++-------- packages/orchestration/src/types.d.ts | 6 +++--- 7 files changed, 27 insertions(+), 26 deletions(-) diff --git a/packages/boot/test/bootstrapTests/test-vat-orchestration.ts b/packages/boot/test/bootstrapTests/test-vat-orchestration.ts index 63d37599fc5..b1652f1fbb4 100644 --- a/packages/boot/test/bootstrapTests/test-vat-orchestration.ts +++ b/packages/boot/test/bootstrapTests/test-vat-orchestration.ts @@ -80,7 +80,7 @@ test('makeAccount returns an ICA connection', async t => { [ EV(account).getRemoteAddress(), EV(account).getLocalAddress(), - EV(account).getAccountAddress(), + EV(account).getAddress(), EV(account).getPort(), ], ); diff --git a/packages/orchestration/src/examples/stakeAtom.contract.js b/packages/orchestration/src/examples/stakeAtom.contract.js index 6f16a8e6451..d7359c09773 100644 --- a/packages/orchestration/src/examples/stakeAtom.contract.js +++ b/packages/orchestration/src/examples/stakeAtom.contract.js @@ -52,12 +52,12 @@ export const start = async (zcf, privateArgs, baggage) => { hostConnectionId, controllerConnectionId, ); - const accountAddress = await E(account).getAccountAddress(); - trace('account address', accountAddress); + const address = await E(account).getAddress(); + trace('chain address', address); const { holder, invitationMakers } = makeStakingAccountKit( account, storageNode, - accountAddress, + address, ); return { publicSubscribers: holder.getPublicTopics(), diff --git a/packages/orchestration/src/examples/swapExample.contract.js b/packages/orchestration/src/examples/swapExample.contract.js index 306abf8ebc7..0b29715757d 100644 --- a/packages/orchestration/src/examples/swapExample.contract.js +++ b/packages/orchestration/src/examples/swapExample.contract.js @@ -52,7 +52,7 @@ export const start = async (zcf, privateArgs) => { agoric.makeAccount(), ]); - const tiaAddress = await celestiaAccount.getChainAddress(); + const tiaAddress = await celestiaAccount.getAddress(); // deposit funds from user seat to LocalChainAccount const seatKit = zcf.makeEmptySeatKit(); diff --git a/packages/orchestration/src/examples/unbondExample.contract.js b/packages/orchestration/src/examples/unbondExample.contract.js index 664d77ea650..949d3c3bc90 100644 --- a/packages/orchestration/src/examples/unbondExample.contract.js +++ b/packages/orchestration/src/examples/unbondExample.contract.js @@ -59,7 +59,7 @@ export const start = async (zcf, privateArgs) => { // TODO the `TIA` string actually needs to be the Brand from AgoricNames const tiaAmt = await celestiaAccount.getBalance('TIA'); - await celestiaAccount.transfer(tiaAmt, strideAccount.getChainAddress()); + await celestiaAccount.transfer(tiaAmt, strideAccount.getAddress()); await strideAccount.liquidStake(tiaAmt); }, diff --git a/packages/orchestration/src/exos/stakingAccountKit.js b/packages/orchestration/src/exos/stakingAccountKit.js index 446a586bff2..5d04b266242 100644 --- a/packages/orchestration/src/exos/stakingAccountKit.js +++ b/packages/orchestration/src/exos/stakingAccountKit.js @@ -14,7 +14,8 @@ import { E } from '@endo/far'; import { Any } from '@agoric/cosmic-proto/google/protobuf/any.js'; /** - * @import { ChainAccount, ChainAddress } from '../types.js'; + * @import { ChainAddress } from '../types.js'; + * @import { ChainAccountKit } from '../service.js'; * @import { RecorderKit, MakeRecorderKit } from '@agoric/zoe/src/contractSupport/recorder.js'; * @import { Baggage } from '@agoric/swingset-liveslots'; * @import {AnyJson} from '@agoric/cosmic-proto'; @@ -25,14 +26,14 @@ const trace = makeTracer('StakingAccountHolder'); const { Fail } = assert; /** * @typedef {object} StakingAccountNotification - * @property {string} address + * @property {ChainAddress['address']} address */ /** * @typedef {{ * topicKit: RecorderKit; - * account: ChainAccount; - * chainAddress: string; + * account: ChainAccountKit['account']; + * address: ChainAddress['address']; * }} State */ @@ -69,16 +70,16 @@ export const prepareStakingAccountKit = (baggage, makeRecorderKit, zcf) => { }), }, /** - * @param {ChainAccount} account + * @param {ChainAccountKit['account']} account * @param {StorageNode} storageNode - * @param {string} chainAddress + * @param {ChainAddress['address']} address * @returns {State} */ - (account, storageNode, chainAddress) => { + (account, storageNode, address) => { // must be the fully synchronous maker because the kit is held in durable state const topicKit = makeRecorderKit(storageNode, PUBLIC_TOPICS.account[1]); - return { account, chainAddress, topicKit }; + return { account, address, topicKit }; }, { helper: { @@ -109,7 +110,7 @@ export const prepareStakingAccountKit = (baggage, makeRecorderKit, zcf) => { }; const account = this.facets.helper.owned(); - const delegatorAddress = this.state.chainAddress; + const delegatorAddress = this.state.address; const result = await E(account).executeEncodedTx([ /** @type {AnyJson} */ ( diff --git a/packages/orchestration/src/service.js b/packages/orchestration/src/service.js index db0d2ddaee7..d3b951ff0d7 100644 --- a/packages/orchestration/src/service.js +++ b/packages/orchestration/src/service.js @@ -59,7 +59,7 @@ export const Proto3Shape = { }; export const ChainAccountI = M.interface('ChainAccount', { - getAccountAddress: M.call().returns(M.string()), + getAddress: M.call().returns(M.string()), getLocalAddress: M.call().returns(M.string()), getRemoteAddress: M.call().returns(M.string()), getPort: M.call().returns(M.remotable('Port')), @@ -96,7 +96,7 @@ const prepareChainAccount = zone => * localAddress: string | undefined; * requestedRemoteAddress: string; * remoteAddress: string | undefined; - * accountAddress: string | undefined; + * address: ChainAddress['address'] | undefined; * }} */ ( harden({ @@ -104,18 +104,18 @@ const prepareChainAccount = zone => connection: undefined, requestedRemoteAddress, remoteAddress: undefined, - accountAddress: undefined, + address: undefined, localAddress: undefined, }) ), { account: { /** - * @returns {string} the address of the account on the chain + * @returns {ChainAddress['address']} the address of the account on the chain */ - getAccountAddress() { + getAddress() { return NonNullish( - this.state.accountAddress, + this.state.address, 'Error parsing account address from remote address', ); }, @@ -194,7 +194,7 @@ const prepareChainAccount = zone => this.state.remoteAddress = remoteAddr; this.state.localAddress = localAddr; // XXX parseAddress currently throws, should it return '' instead? - this.state.accountAddress = parseAddress(remoteAddr); + this.state.address = parseAddress(remoteAddr); }, async onClose(_connection, reason) { trace(`ICA Channel closed. Reason: ${reason}`); @@ -252,7 +252,7 @@ const prepareOrchestration = (zone, createChainAccount) => * the counterparty connection_id * @param {IBCConnectionID} controllerConnectionId * self connection_id - * @returns {Promise} + * @returns {Promise} */ async makeAccount(hostConnectionId, controllerConnectionId) { const port = await this.facets.self.bindPort(); diff --git a/packages/orchestration/src/types.d.ts b/packages/orchestration/src/types.d.ts index 09d90834c15..a20e1359376 100644 --- a/packages/orchestration/src/types.d.ts +++ b/packages/orchestration/src/types.d.ts @@ -236,7 +236,7 @@ export interface ChainAccount { /** * @returns the address of the account on the remote chain */ - getAccountAddress: () => string; + getAddress: () => ChainAddress; /** * Submit a transaction on behalf of the remote account for execution on the remote chain. * @param msgs - records for the transaction @@ -271,7 +271,7 @@ export interface BaseOrchestrationAccount { /** * @returns the address of the account on the remote chain */ - getChainAddress: () => ChainAddress; + getAddress: () => ChainAddress; /** @returns an array of amounts for every balance in the account. */ getBalances: () => Promise; @@ -427,7 +427,7 @@ export type TransferMsg = { // Example // await icaNoble.transferSteps(usdcAmt, -// osmosisSwap(tiaBrand, { pool: 1224, slippage: 0.05 }, icaCel.getAddress())); +// osmosisSwap(tiaBrand, { pool: 1224, slippage: 0.05 }, icaCel.getChainAddress())); /** * @param pool - Required. Pool number From 05cce523c6a544a840e345ff420e2dadcce0ce4f Mon Sep 17 00:00:00 2001 From: 0xPatrick Date: Wed, 1 May 2024 16:07:24 -0400 Subject: [PATCH 4/7] refactor(localchain): createAccount -> makeAccount --- .../boot/test/bootstrapTests/test-orchestration.ts | 2 +- .../src/examples/stakeAtom.contract.js | 4 ++-- .../src/examples/stakeBld.contract.js | 2 +- packages/orchestration/src/types.d.ts | 8 +------- packages/vats/src/localchain.js | 14 +++++++------- packages/vats/src/proposals/localchain-test.js | 2 +- 6 files changed, 13 insertions(+), 19 deletions(-) diff --git a/packages/boot/test/bootstrapTests/test-orchestration.ts b/packages/boot/test/bootstrapTests/test-orchestration.ts index f61088d1cc3..d105d481dc8 100644 --- a/packages/boot/test/bootstrapTests/test-orchestration.ts +++ b/packages/boot/test/bootstrapTests/test-orchestration.ts @@ -140,7 +140,7 @@ test.serial('stakeAtom - smart wallet', async t => { invitationSpec: { source: 'agoricContract', instancePath: ['stakeAtom'], - callPipe: [['makeAccountInvitation']], + callPipe: [['makeNewAccountInvitation']], }, proposal: {}, }); diff --git a/packages/orchestration/src/examples/stakeAtom.contract.js b/packages/orchestration/src/examples/stakeAtom.contract.js index d7359c09773..6635477224e 100644 --- a/packages/orchestration/src/examples/stakeAtom.contract.js +++ b/packages/orchestration/src/examples/stakeAtom.contract.js @@ -70,14 +70,14 @@ export const start = async (zcf, privateArgs, baggage) => { 'StakeAtom', M.interface('StakeAtomI', { makeAccount: M.callWhen().returns(M.remotable('ChainAccount')), - makeAccountInvitation: M.call().returns(M.promise()), + makeNewAccountInvitation: M.call().returns(M.promise()), }), { async makeAccount() { trace('makeAccount'); return makeAccount().then(({ account }) => account); }, - makeAccountInvitation() { + makeNewAccountInvitation() { trace('makeCreateAccountInvitation'); return zcf.makeInvitation( async seat => { diff --git a/packages/orchestration/src/examples/stakeBld.contract.js b/packages/orchestration/src/examples/stakeBld.contract.js index 57a37c7ebd2..8b0a165f901 100644 --- a/packages/orchestration/src/examples/stakeBld.contract.js +++ b/packages/orchestration/src/examples/stakeBld.contract.js @@ -48,7 +48,7 @@ export const start = async (zcf, privateArgs, baggage) => { const { give } = seat.getProposal(); trace('makeStakeBldInvitation', give); // XXX type appears local but it's remote - const account = await E(privateArgs.localchain).createAccount(); + const account = await E(privateArgs.localchain).makeAccount(); const lcaSeatKit = zcf.makeEmptySeatKit(); atomicTransfer(zcf, seat, lcaSeatKit.zcfSeat, give); seat.exit(); diff --git a/packages/orchestration/src/types.d.ts b/packages/orchestration/src/types.d.ts index a20e1359376..59de8f0c029 100644 --- a/packages/orchestration/src/types.d.ts +++ b/packages/orchestration/src/types.d.ts @@ -1,10 +1,4 @@ -import type { - Amount, - Brand, - Payment, - Purse, - RemotableBrand, -} from '@agoric/ertp/exported.js'; +import type { Amount, Brand, Payment, Purse } from '@agoric/ertp/exported.js'; import type { Timestamp } from '@agoric/time'; import type { Invitation } from '@agoric/zoe/exported.js'; import type { Any } from '@agoric/cosmic-proto/google/protobuf/any'; diff --git a/packages/vats/src/localchain.js b/packages/vats/src/localchain.js index 677009cb4d5..ade92745c56 100644 --- a/packages/vats/src/localchain.js +++ b/packages/vats/src/localchain.js @@ -95,7 +95,7 @@ const prepareLocalChainAccount = zone => */ export const LocalChainI = M.interface('LocalChain', { - createAccount: M.callWhen().returns(M.remotable('LocalChainAccount')), + makeAccount: M.callWhen().returns(M.remotable('LocalChainAccount')), query: M.callWhen(M.record()).returns(M.record()), queryMany: M.callWhen(M.arrayOf(M.record())).returns(M.arrayOf(M.record())), }); @@ -106,9 +106,9 @@ export const LocalChainAdminI = M.interface('LocalChainAdmin', { /** * @param {import('@agoric/base-zone').Zone} zone - * @param {ReturnType} createAccount + * @param {ReturnType} makeAccount */ -const prepareLocalChain = (zone, createAccount) => +const prepareLocalChain = (zone, makeAccount) => zone.exoClassKit( 'LocalChain', { public: LocalChainI, admin: LocalChainAdminI }, @@ -151,13 +151,13 @@ const prepareLocalChain = (zone, createAccount) => * x/vlocalchain/keeper/keeper.go AllocateAddress for the use of the app * hash and block data hash. */ - async createAccount() { + async makeAccount() { const { powers } = this.state; const system = getPower(powers, 'system'); const address = await E(system).toBridge({ type: 'VLOCALCHAIN_ALLOCATE_ADDRESS', }); - return createAccount(address, powers); + return makeAccount(address, powers); }, /** * Make a single query to the local chain. Will reject with an error if @@ -205,8 +205,8 @@ const prepareLocalChain = (zone, createAccount) => /** @param {import('@agoric/base-zone').Zone} zone */ export const prepareLocalChainTools = zone => { - const createAccount = prepareLocalChainAccount(zone); - const makeLocalChain = prepareLocalChain(zone, createAccount); + const makeAccount = prepareLocalChainAccount(zone); + const makeLocalChain = prepareLocalChain(zone, makeAccount); return harden({ makeLocalChain }); }; diff --git a/packages/vats/src/proposals/localchain-test.js b/packages/vats/src/proposals/localchain-test.js index 5ff95dbfa7c..8d91a3be57a 100644 --- a/packages/vats/src/proposals/localchain-test.js +++ b/packages/vats/src/proposals/localchain-test.js @@ -28,7 +28,7 @@ export const testLocalChain = async ( let result; try { - const lca = await E(localchain).createAccount(); + const lca = await E(localchain).makeAccount(); console.info('created account', lca); const address = await E(lca).getAddress(); console.info('address', address); From f2a6d41b15f6cf0addb003dcf1bd0d424fd50abd Mon Sep 17 00:00:00 2001 From: 0xPatrick Date: Wed, 1 May 2024 19:13:09 -0400 Subject: [PATCH 5/7] refactor(orchestration): prefer ChainAddress object to string --- .../test/bootstrapTests/test-orchestration.ts | 2 +- .../bootstrapTests/test-vat-orchestration.ts | 19 +++++----- .../src/examples/stakeAtom.contract.js | 4 +-- .../src/exos/stakingAccountKit.js | 19 +++++----- packages/orchestration/src/service.js | 35 ++++++++++++++----- packages/orchestration/src/types.d.ts | 7 ++-- 6 files changed, 50 insertions(+), 36 deletions(-) diff --git a/packages/boot/test/bootstrapTests/test-orchestration.ts b/packages/boot/test/bootstrapTests/test-orchestration.ts index d105d481dc8..6a9ff4f6f5d 100644 --- a/packages/boot/test/bootstrapTests/test-orchestration.ts +++ b/packages/boot/test/bootstrapTests/test-orchestration.ts @@ -140,7 +140,7 @@ test.serial('stakeAtom - smart wallet', async t => { invitationSpec: { source: 'agoricContract', instancePath: ['stakeAtom'], - callPipe: [['makeNewAccountInvitation']], + callPipe: [['makeMakeAccountInvitation']], }, proposal: {}, }); diff --git a/packages/boot/test/bootstrapTests/test-vat-orchestration.ts b/packages/boot/test/bootstrapTests/test-vat-orchestration.ts index b1652f1fbb4..c5c3887ffcc 100644 --- a/packages/boot/test/bootstrapTests/test-vat-orchestration.ts +++ b/packages/boot/test/bootstrapTests/test-vat-orchestration.ts @@ -76,22 +76,21 @@ test('makeAccount returns an ICA connection', async t => { matches(account, M.remotable('ChainAccount')), 'account is a remotable', ); - const [remoteAddress, localAddress, accountAddress, port] = await Promise.all( - [ - EV(account).getRemoteAddress(), - EV(account).getLocalAddress(), - EV(account).getAddress(), - EV(account).getPort(), - ], - ); + const [remoteAddress, localAddress, chainAddress, port] = await Promise.all([ + EV(account).getRemoteAddress(), + EV(account).getLocalAddress(), + EV(account).getAddress(), + EV(account).getPort(), + ]); t.regex(remoteAddress, /icahost/); t.regex(localAddress, /icacontroller/); - t.regex(accountAddress, /cosmos1/); + t.regex(chainAddress.address, /cosmos1/); + t.regex(chainAddress.chainId, /FIXME/); // TODO, use a real chainId #9063 t.truthy(matches(port, M.remotable('Port'))); t.log('ICA Account Addresses', { remoteAddress, localAddress, - accountAddress, + chainAddress, }); }); diff --git a/packages/orchestration/src/examples/stakeAtom.contract.js b/packages/orchestration/src/examples/stakeAtom.contract.js index 6635477224e..0b9baaafeae 100644 --- a/packages/orchestration/src/examples/stakeAtom.contract.js +++ b/packages/orchestration/src/examples/stakeAtom.contract.js @@ -70,14 +70,14 @@ export const start = async (zcf, privateArgs, baggage) => { 'StakeAtom', M.interface('StakeAtomI', { makeAccount: M.callWhen().returns(M.remotable('ChainAccount')), - makeNewAccountInvitation: M.call().returns(M.promise()), + makeMakeAccountInvitation: M.call().returns(M.promise()), }), { async makeAccount() { trace('makeAccount'); return makeAccount().then(({ account }) => account); }, - makeNewAccountInvitation() { + makeMakeAccountInvitation() { trace('makeCreateAccountInvitation'); return zcf.makeInvitation( async seat => { diff --git a/packages/orchestration/src/exos/stakingAccountKit.js b/packages/orchestration/src/exos/stakingAccountKit.js index 5d04b266242..4b2a687a9c8 100644 --- a/packages/orchestration/src/exos/stakingAccountKit.js +++ b/packages/orchestration/src/exos/stakingAccountKit.js @@ -14,8 +14,7 @@ import { E } from '@endo/far'; import { Any } from '@agoric/cosmic-proto/google/protobuf/any.js'; /** - * @import { ChainAddress } from '../types.js'; - * @import { ChainAccountKit } from '../service.js'; + * @import { ChainAccount, ChainAddress } from '../types.js'; * @import { RecorderKit, MakeRecorderKit } from '@agoric/zoe/src/contractSupport/recorder.js'; * @import { Baggage } from '@agoric/swingset-liveslots'; * @import {AnyJson} from '@agoric/cosmic-proto'; @@ -26,14 +25,14 @@ const trace = makeTracer('StakingAccountHolder'); const { Fail } = assert; /** * @typedef {object} StakingAccountNotification - * @property {ChainAddress['address']} address + * @property {ChainAddress} chainAddress */ /** * @typedef {{ * topicKit: RecorderKit; - * account: ChainAccountKit['account']; - * address: ChainAddress['address']; + * account: ChainAccount; + * chainAddress: ChainAddress; * }} State */ @@ -70,16 +69,16 @@ export const prepareStakingAccountKit = (baggage, makeRecorderKit, zcf) => { }), }, /** - * @param {ChainAccountKit['account']} account + * @param {ChainAccount} account * @param {StorageNode} storageNode - * @param {ChainAddress['address']} address + * @param {ChainAddress} chainAddress * @returns {State} */ - (account, storageNode, address) => { + (account, storageNode, chainAddress) => { // must be the fully synchronous maker because the kit is held in durable state const topicKit = makeRecorderKit(storageNode, PUBLIC_TOPICS.account[1]); - return { account, address, topicKit }; + return { account, chainAddress, topicKit }; }, { helper: { @@ -110,7 +109,7 @@ export const prepareStakingAccountKit = (baggage, makeRecorderKit, zcf) => { }; const account = this.facets.helper.owned(); - const delegatorAddress = this.state.address; + const delegatorAddress = this.state.chainAddress.address; const result = await E(account).executeEncodedTx([ /** @type {AnyJson} */ ( diff --git a/packages/orchestration/src/service.js b/packages/orchestration/src/service.js index d3b951ff0d7..3e816703ffb 100644 --- a/packages/orchestration/src/service.js +++ b/packages/orchestration/src/service.js @@ -19,7 +19,7 @@ import { makeTxPacket, parsePacketAck } from './utils/tx.js'; * @import { IBCConnectionID } from '@agoric/vats'; * @import { Zone } from '@agoric/base-zone'; * @import { TxBody } from '@agoric/cosmic-proto/cosmos/tx/v1beta1/tx.js'; - * @import { AttenuatedNetwork, ChainAccount, ChainAddress } from './types.js'; + * @import { ChainAccount, ChainAddress } from './types.js'; */ const { Fail, bare } = assert; @@ -58,8 +58,17 @@ export const Proto3Shape = { value: M.string(), }; +export const ChainAddressShape = { + address: M.string(), + chainId: M.string(), + addressEncoding: M.string(), +}; + +/** @typedef {'UNPARSABLE_CHAIN_ADDRESS'} UnparsableChainAddress */ +const UNPARSABLE_CHAIN_ADDRESS = 'UNPARSABLE_CHAIN_ADDRESS'; + export const ChainAccountI = M.interface('ChainAccount', { - getAddress: M.call().returns(M.string()), + getAddress: M.call().returns(ChainAddressShape), getLocalAddress: M.call().returns(M.string()), getRemoteAddress: M.call().returns(M.string()), getPort: M.call().returns(M.remotable('Port')), @@ -96,7 +105,7 @@ const prepareChainAccount = zone => * localAddress: string | undefined; * requestedRemoteAddress: string; * remoteAddress: string | undefined; - * address: ChainAddress['address'] | undefined; + * chainAddress: ChainAddress | undefined; * }} */ ( harden({ @@ -104,19 +113,19 @@ const prepareChainAccount = zone => connection: undefined, requestedRemoteAddress, remoteAddress: undefined, - address: undefined, + chainAddress: undefined, localAddress: undefined, }) ), { account: { /** - * @returns {ChainAddress['address']} the address of the account on the chain + * @returns {ChainAddress} */ getAddress() { return NonNullish( - this.state.address, - 'Error parsing account address from remote address', + this.state.chainAddress, + 'ICA channel creation acknowledgement not yet received.', ); }, getLocalAddress() { @@ -194,7 +203,15 @@ const prepareChainAccount = zone => this.state.remoteAddress = remoteAddr; this.state.localAddress = localAddr; // XXX parseAddress currently throws, should it return '' instead? - this.state.address = parseAddress(remoteAddr); + this.state.chainAddress = harden({ + address: parseAddress(remoteAddr) || UNPARSABLE_CHAIN_ADDRESS, + // TODO get this from `Chain` object #9063 + // XXX how do we get a chainId for an unknown chain? seems it may need to be a user supplied arg + chainId: 'FIXME', + addressEncoding: 'bech32', + }); + trace('got chainAddress', this.state.chainAddress); + trace('parseAddress(remoteAddr)', parseAddress(remoteAddr)); }, async onClose(_connection, reason) { trace(`ICA Channel closed. Reason: ${reason}`); @@ -252,7 +269,7 @@ const prepareOrchestration = (zone, createChainAccount) => * the counterparty connection_id * @param {IBCConnectionID} controllerConnectionId * self connection_id - * @returns {Promise} + * @returns {Promise} */ async makeAccount(hostConnectionId, controllerConnectionId) { const port = await this.facets.self.bindPort(); diff --git a/packages/orchestration/src/types.d.ts b/packages/orchestration/src/types.d.ts index 59de8f0c029..9a71dd1e055 100644 --- a/packages/orchestration/src/types.d.ts +++ b/packages/orchestration/src/types.d.ts @@ -419,12 +419,11 @@ export type TransferMsg = { data?: object; }; -// Example -// await icaNoble.transferSteps(usdcAmt, -// osmosisSwap(tiaBrand, { pool: 1224, slippage: 0.05 }, icaCel.getChainAddress())); - /** * @param pool - Required. Pool number + * @example + * await icaNoble.transferSteps(usdcAmt, + * osmosisSwap(tiaBrand, { pool: 1224, slippage: 0.05 }, icaCel.getAddress())); */ export type OsmoSwapOptions = { pool: string; From 678f21f51b8ad94f9064dcd8b4b3bbad707b6996 Mon Sep 17 00:00:00 2001 From: 0xPatrick Date: Thu, 2 May 2024 10:39:28 -0400 Subject: [PATCH 6/7] feat(orchestration): align ChainAccount spec with current implementation --- .../bootstrapTests/test-vat-orchestration.ts | 10 +++--- packages/orchestration/src/service.js | 9 +++--- packages/orchestration/src/types.d.ts | 32 +++++++++++++++---- 3 files changed, 37 insertions(+), 14 deletions(-) diff --git a/packages/boot/test/bootstrapTests/test-vat-orchestration.ts b/packages/boot/test/bootstrapTests/test-vat-orchestration.ts index c5c3887ffcc..fb643b16d0f 100644 --- a/packages/boot/test/bootstrapTests/test-vat-orchestration.ts +++ b/packages/boot/test/bootstrapTests/test-vat-orchestration.ts @@ -7,7 +7,7 @@ import { MsgDelegateResponse, } from '@agoric/cosmic-proto/cosmos/staking/v1beta1/tx.js'; import { Any } from '@agoric/cosmic-proto/google/protobuf/any.js'; -import type { ChainAccount, OrchestrationService } from '@agoric/orchestration'; +import type { OrchestrationService } from '@agoric/orchestration'; import { decodeBase64 } from '@endo/base64'; import { M, matches } from '@endo/patterns'; import { makeWalletFactoryContext } from './walletFactory.ts'; @@ -65,7 +65,8 @@ test('makeAccount returns an ICA connection', async t => { runUtils: { EV }, } = t.context; - const orchestration = await EV.vat('bootstrap').consumeItem('orchestration'); + const orchestration: OrchestrationService = + await EV.vat('bootstrap').consumeItem('orchestration'); const account = await EV(orchestration).makeAccount( 'connection-0', @@ -120,9 +121,10 @@ test('ICA connection can send msg with proto3', async t => { runUtils: { EV }, } = t.context; - const orchestration = await EV.vat('bootstrap').consumeItem('orchestration'); + const orchestration: OrchestrationService = + await EV.vat('bootstrap').consumeItem('orchestration'); - const account: ChainAccount = await EV(orchestration).makeAccount( + const account = await EV(orchestration).makeAccount( 'connection-0', 'connection-0', ); diff --git a/packages/orchestration/src/service.js b/packages/orchestration/src/service.js index 3e816703ffb..b68d00a4bd9 100644 --- a/packages/orchestration/src/service.js +++ b/packages/orchestration/src/service.js @@ -20,6 +20,7 @@ import { makeTxPacket, parsePacketAck } from './utils/tx.js'; * @import { Zone } from '@agoric/base-zone'; * @import { TxBody } from '@agoric/cosmic-proto/cosmos/tx/v1beta1/tx.js'; * @import { ChainAccount, ChainAddress } from './types.js'; + * @import { LocalIbcAddress, RemoteIbcAddress } from '@agoric/vats/tools/ibc-utils.js'; */ const { Fail, bare } = assert; @@ -102,9 +103,9 @@ const prepareChainAccount = zone => * @type {{ * port: Port; * connection: Remote | undefined; - * localAddress: string | undefined; + * localAddress: LocalIbcAddress | undefined; * requestedRemoteAddress: string; - * remoteAddress: string | undefined; + * remoteAddress: RemoteIbcAddress | undefined; * chainAddress: ChainAddress | undefined; * }} */ ( @@ -194,8 +195,8 @@ const prepareChainAccount = zone => connectionHandler: { /** * @param {Remote} connection - * @param {string} localAddr - * @param {string} remoteAddr + * @param {LocalIbcAddress} localAddr + * @param {RemoteIbcAddress} remoteAddr */ async onOpen(connection, localAddr, remoteAddr) { trace(`ICA Channel Opened for ${localAddr} at ${remoteAddr}`); diff --git a/packages/orchestration/src/types.d.ts b/packages/orchestration/src/types.d.ts index 9a71dd1e055..5d33dc6a241 100644 --- a/packages/orchestration/src/types.d.ts +++ b/packages/orchestration/src/types.d.ts @@ -12,6 +12,12 @@ import type { Redelegation, UnbondingDelegation, } from '@agoric/cosmic-proto/cosmos/staking/v1beta1/staking.js'; +import type { TxBody } from '@agoric/cosmic-proto/cosmos/tx/v1beta1/tx.js'; +import type { + LocalIbcAddress, + RemoteIbcAddress, +} from '@agoric/vats/tools/ibc-utils.js'; +import type { Port } from '@agoric/network'; /** * static declaration of known chain types will allow type support for @@ -239,10 +245,14 @@ export interface ChainAccount { executeTx: (msgs: Proto3JSONMsg[]) => Promise; /** * Submit a transaction on behalf of the remote account for execution on the remote chain. - * @param msgs - records for the transaction - * @returns acknowledge string + * @param {AnyJson[]} msgs - records for the transaction + * @param {Partial>} [opts] - optional parameters for the Tx, like `timeoutHeight` and `memo` + * @returns acknowledgement string */ - executeEncodedTx: (msgs: AnyJson[]) => Promise; + executeEncodedTx: ( + 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 */ @@ -253,6 +263,12 @@ export interface ChainAccount { close: () => Promise; /* transfer account to new holder */ prepareTransfer: () => Promise; + /** @returns the address of the remote channel */ + getRemoteAddress: () => RemoteIbcAddress; + /** @returns the address of the local channel */ + getLocalAddress: () => LocalIbcAddress; + /** @returns the port the ICA channel is bound to */ + getPort: () => Port; } /** @@ -442,6 +458,10 @@ export type OsmoSwapFn = ( next: TransferMsg | ChainAddress, ) => TransferMsg; -type AfterAction = { destChain: string; destAddress: ChainAddress }; -type SwapExact = { amountIn: Amount; amountOut: Amount }; -type SwapMaxSlippage = { amountIn: Amount; brandOut: Brand; slippage: number }; +export type AfterAction = { destChain: string; destAddress: ChainAddress }; +export type SwapExact = { amountIn: Amount; amountOut: Amount }; +export type SwapMaxSlippage = { + amountIn: Amount; + brandOut: Brand; + slippage: number; +}; From 39a980a64c899a746f1c5108d98440bdb3374d68 Mon Sep 17 00:00:00 2001 From: 0xPatrick Date: Thu, 2 May 2024 10:58:16 -0400 Subject: [PATCH 7/7] refactor(chainAccountKit): move to own file --- .../test/bootstrapTests/test-orchestration.ts | 2 +- .../bootstrapTests/test-vat-orchestration.ts | 1 - .../src/examples/stakeAtom.contract.js | 4 +- .../orchestration/src/exos/chainAccountKit.js | 200 +++++++++++++++++ packages/orchestration/src/service.js | 209 ++---------------- packages/orchestration/src/types.d.ts | 14 +- 6 files changed, 226 insertions(+), 204 deletions(-) create mode 100644 packages/orchestration/src/exos/chainAccountKit.js diff --git a/packages/boot/test/bootstrapTests/test-orchestration.ts b/packages/boot/test/bootstrapTests/test-orchestration.ts index 6a9ff4f6f5d..f1ab0b27b20 100644 --- a/packages/boot/test/bootstrapTests/test-orchestration.ts +++ b/packages/boot/test/bootstrapTests/test-orchestration.ts @@ -140,7 +140,7 @@ test.serial('stakeAtom - smart wallet', async t => { invitationSpec: { source: 'agoricContract', instancePath: ['stakeAtom'], - callPipe: [['makeMakeAccountInvitation']], + callPipe: [['makeAcountInvitationMaker']], }, proposal: {}, }); diff --git a/packages/boot/test/bootstrapTests/test-vat-orchestration.ts b/packages/boot/test/bootstrapTests/test-vat-orchestration.ts index fb643b16d0f..f4151cd6e10 100644 --- a/packages/boot/test/bootstrapTests/test-vat-orchestration.ts +++ b/packages/boot/test/bootstrapTests/test-vat-orchestration.ts @@ -150,7 +150,6 @@ test('ICA connection can send msg with proto3', async t => { const txWithOptions = await EV(account).executeEncodedTx( [delegateMsgSuccess], - // @ts-expect-error XXX TxBody interface { memo: 'TESTING', timeoutHeight: 1_000_000_000n, diff --git a/packages/orchestration/src/examples/stakeAtom.contract.js b/packages/orchestration/src/examples/stakeAtom.contract.js index 0b9baaafeae..ca885cb12a0 100644 --- a/packages/orchestration/src/examples/stakeAtom.contract.js +++ b/packages/orchestration/src/examples/stakeAtom.contract.js @@ -70,14 +70,14 @@ export const start = async (zcf, privateArgs, baggage) => { 'StakeAtom', M.interface('StakeAtomI', { makeAccount: M.callWhen().returns(M.remotable('ChainAccount')), - makeMakeAccountInvitation: M.call().returns(M.promise()), + makeAcountInvitationMaker: M.call().returns(M.promise()), }), { async makeAccount() { trace('makeAccount'); return makeAccount().then(({ account }) => account); }, - makeMakeAccountInvitation() { + makeAcountInvitationMaker() { trace('makeCreateAccountInvitation'); return zcf.makeInvitation( async seat => { diff --git a/packages/orchestration/src/exos/chainAccountKit.js b/packages/orchestration/src/exos/chainAccountKit.js new file mode 100644 index 00000000000..1013b21f769 --- /dev/null +++ b/packages/orchestration/src/exos/chainAccountKit.js @@ -0,0 +1,200 @@ +// @ts-check +/** @file Orchestration service */ +import { NonNullish } from '@agoric/assert'; +import { makeTracer } from '@agoric/internal'; + +// XXX ambient types runtime imports until https://github.com/Agoric/agoric-sdk/issues/6512 +import '@agoric/network/exported.js'; + +import { V as E } from '@agoric/vat-data/vow.js'; +import { M } from '@endo/patterns'; +import { PaymentShape, PurseShape } from '@agoric/ertp'; +import { InvitationShape } from '@agoric/zoe/src/typeGuards.js'; +import { parseAddress } from '../utils/address.js'; +import { makeTxPacket, parsePacketAck } from '../utils/tx.js'; + +/** + * @import { Connection, Port } from '@agoric/network'; + * @import { Remote } from '@agoric/vow'; + * @import { Zone } from '@agoric/base-zone'; + * @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 { ChainAddress } from '../types.js'; + */ + +const { Fail } = assert; +const trace = makeTracer('ChainAccount'); + +export const Proto3Shape = { + typeUrl: M.string(), + value: M.string(), +}; + +export const ChainAddressShape = { + address: M.string(), + chainId: M.string(), + addressEncoding: M.string(), +}; + +/** @typedef {'UNPARSABLE_CHAIN_ADDRESS'} UnparsableChainAddress */ +const UNPARSABLE_CHAIN_ADDRESS = 'UNPARSABLE_CHAIN_ADDRESS'; + +export const ChainAccountI = M.interface('ChainAccount', { + getAddress: M.call().returns(ChainAddressShape), + getLocalAddress: M.call().returns(M.string()), + getRemoteAddress: M.call().returns(M.string()), + getPort: M.call().returns(M.remotable('Port')), + executeTx: M.call(M.arrayOf(M.record())).returns(M.promise()), + executeEncodedTx: M.call(M.arrayOf(Proto3Shape)) + .optional(M.record()) + .returns(M.promise()), + close: M.callWhen().returns(M.undefined()), + deposit: M.callWhen(PaymentShape).returns(M.undefined()), + getPurse: M.callWhen().returns(PurseShape), + prepareTransfer: M.callWhen().returns(InvitationShape), +}); + +export const ConnectionHandlerI = M.interface('ConnectionHandler', { + onOpen: M.callWhen(M.any(), M.string(), M.string(), M.any()).returns(M.any()), + onClose: M.callWhen(M.any(), M.any(), M.any()).returns(M.any()), + onReceive: M.callWhen(M.any(), M.string()).returns(M.any()), +}); + +/** @param {Zone} zone */ +export const prepareChainAccountKit = zone => + zone.exoClassKit( + 'ChainAccount', + { account: ChainAccountI, connectionHandler: ConnectionHandlerI }, + /** + * @param {Port} port + * @param {string} requestedRemoteAddress + */ + (port, requestedRemoteAddress) => + /** + * @type {{ + * port: Port; + * connection: Remote | undefined; + * localAddress: LocalIbcAddress | undefined; + * requestedRemoteAddress: string; + * remoteAddress: RemoteIbcAddress | undefined; + * chainAddress: ChainAddress | undefined; + * }} + */ ( + harden({ + port, + connection: undefined, + requestedRemoteAddress, + remoteAddress: undefined, + chainAddress: undefined, + localAddress: undefined, + }) + ), + { + account: { + /** + * @returns {ChainAddress} + */ + getAddress() { + return NonNullish( + this.state.chainAddress, + 'ICA channel creation acknowledgement not yet received.', + ); + }, + getLocalAddress() { + return NonNullish( + this.state.localAddress, + 'local address not available', + ); + }, + getRemoteAddress() { + return NonNullish( + this.state.remoteAddress, + 'remote address not available', + ); + }, + getPort() { + return this.state.port; + }, + executeTx() { + throw new Error('not yet implemented'); + }, + /** + * Submit a transaction on behalf of the remote account for execution on the remote chain. + * @param {AnyJson[]} msgs + * @param {Omit} [opts] + * @returns {Promise} - base64 encoded bytes string. Can be decoded using the corresponding `Msg*Response` object. + * @throws {Error} if packet fails to send or an error is returned + */ + executeEncodedTx(msgs, opts) { + const { connection } = this.state; + if (!connection) throw Fail`connection not available`; + return E.when( + E(connection).send(makeTxPacket(msgs, opts)), + // if parsePacketAck cannot find a `result` key, it throws + ack => parsePacketAck(ack), + ); + }, + /** + * Close the remote account + */ + async close() { + /// XXX what should the behavior be here? and `onClose`? + // - retrieve assets? + // - revoke the port? + const { connection } = this.state; + 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 + * @param {Brand} brand + */ + async getPurse(brand) { + console.log('getPurse got', brand); + throw new Error('not yet implemented'); + }, + + /* transfer account to new holder */ + async prepareTransfer() { + throw new Error('not yet implemented'); + }, + }, + connectionHandler: { + /** + * @param {Remote} connection + * @param {LocalIbcAddress} localAddr + * @param {RemoteIbcAddress} remoteAddr + */ + async onOpen(connection, localAddr, remoteAddr) { + trace(`ICA Channel Opened for ${localAddr} at ${remoteAddr}`); + this.state.connection = connection; + this.state.remoteAddress = remoteAddr; + this.state.localAddress = localAddr; + // XXX parseAddress currently throws, should it return '' instead? + this.state.chainAddress = harden({ + address: parseAddress(remoteAddr) || UNPARSABLE_CHAIN_ADDRESS, + // TODO get this from `Chain` object #9063 + // XXX how do we get a chainId for an unknown chain? seems it may need to be a user supplied arg + chainId: 'FIXME', + addressEncoding: 'bech32', + }); + }, + async onClose(_connection, reason) { + trace(`ICA Channel closed. Reason: ${reason}`); + // XXX handle connection closing + // XXX is there a scenario where a connection will unexpectedly close? _I think yes_ + }, + async onReceive(connection, bytes) { + trace(`ICA Channel onReceive`, connection, bytes); + return ''; + }, + }, + }, + ); + +/** @typedef {ReturnType>} ChainAccountKit */ diff --git a/packages/orchestration/src/service.js b/packages/orchestration/src/service.js index b68d00a4bd9..58baaa3a63a 100644 --- a/packages/orchestration/src/service.js +++ b/packages/orchestration/src/service.js @@ -1,32 +1,22 @@ // @ts-check /** @file Orchestration service */ -import { NonNullish } from '@agoric/assert'; -import { makeTracer } from '@agoric/internal'; // XXX ambient types runtime imports until https://github.com/Agoric/agoric-sdk/issues/6512 import '@agoric/network/exported.js'; import { V as E } from '@agoric/vat-data/vow.js'; import { M } from '@endo/patterns'; -import { PaymentShape, PurseShape } from '@agoric/ertp'; -import { InvitationShape } from '@agoric/zoe/src/typeGuards.js'; -import { makeICAConnectionAddress, parseAddress } from './utils/address.js'; -import { makeTxPacket, parsePacketAck } from './utils/tx.js'; +import { prepareChainAccountKit } from './exos/chainAccountKit.js'; +import { makeICAConnectionAddress } from './utils/address.js'; /** - * @import {Connection, Port, PortAllocator} from '@agoric/network'; - * @import {Remote} from '@agoric/vow'; + * @import { PortAllocator} from '@agoric/network'; * @import { IBCConnectionID } from '@agoric/vats'; * @import { Zone } from '@agoric/base-zone'; - * @import { TxBody } from '@agoric/cosmic-proto/cosmos/tx/v1beta1/tx.js'; - * @import { ChainAccount, ChainAddress } from './types.js'; - * @import { LocalIbcAddress, RemoteIbcAddress } from '@agoric/vats/tools/ibc-utils.js'; + * @import { ChainAccount } from './types.js'; */ const { Fail, bare } = assert; -const trace = makeTracer('Orchestration'); - -/** @import {AnyJson} from '@agoric/cosmic-proto'; */ /** * @typedef {object} OrchestrationPowers @@ -54,179 +44,6 @@ const getPower = (powers, name) => { return /** @type {OrchestrationPowers[K]} */ (powers.get(name)); }; -export const Proto3Shape = { - typeUrl: M.string(), - value: M.string(), -}; - -export const ChainAddressShape = { - address: M.string(), - chainId: M.string(), - addressEncoding: M.string(), -}; - -/** @typedef {'UNPARSABLE_CHAIN_ADDRESS'} UnparsableChainAddress */ -const UNPARSABLE_CHAIN_ADDRESS = 'UNPARSABLE_CHAIN_ADDRESS'; - -export const ChainAccountI = M.interface('ChainAccount', { - getAddress: M.call().returns(ChainAddressShape), - getLocalAddress: M.call().returns(M.string()), - getRemoteAddress: M.call().returns(M.string()), - getPort: M.call().returns(M.remotable('Port')), - executeTx: M.call(M.arrayOf(M.record())).returns(M.promise()), - executeEncodedTx: M.call(M.arrayOf(Proto3Shape)) - .optional(M.record()) - .returns(M.promise()), - close: M.callWhen().returns(M.undefined()), - deposit: M.callWhen(PaymentShape).returns(M.undefined()), - getPurse: M.callWhen().returns(PurseShape), - prepareTransfer: M.callWhen().returns(InvitationShape), -}); - -export const ConnectionHandlerI = M.interface('ConnectionHandler', { - onOpen: M.callWhen(M.any(), M.string(), M.string(), M.any()).returns(M.any()), - onClose: M.callWhen(M.any(), M.any(), M.any()).returns(M.any()), - onReceive: M.callWhen(M.any(), M.string()).returns(M.any()), -}); - -/** @param {Zone} zone */ -const prepareChainAccount = zone => - zone.exoClassKit( - 'ChainAccount', - { account: ChainAccountI, connectionHandler: ConnectionHandlerI }, - /** - * @param {Port} port - * @param {string} requestedRemoteAddress - */ - (port, requestedRemoteAddress) => - /** - * @type {{ - * port: Port; - * connection: Remote | undefined; - * localAddress: LocalIbcAddress | undefined; - * requestedRemoteAddress: string; - * remoteAddress: RemoteIbcAddress | undefined; - * chainAddress: ChainAddress | undefined; - * }} - */ ( - harden({ - port, - connection: undefined, - requestedRemoteAddress, - remoteAddress: undefined, - chainAddress: undefined, - localAddress: undefined, - }) - ), - { - account: { - /** - * @returns {ChainAddress} - */ - getAddress() { - return NonNullish( - this.state.chainAddress, - 'ICA channel creation acknowledgement not yet received.', - ); - }, - getLocalAddress() { - return NonNullish( - this.state.localAddress, - 'local address not available', - ); - }, - getRemoteAddress() { - return NonNullish( - this.state.remoteAddress, - 'remote address not available', - ); - }, - getPort() { - return this.state.port; - }, - executeTx() { - throw new Error('not yet implemented'); - }, - /** - * Submit a transaction on behalf of the remote account for execution on the remote chain. - * @param {AnyJson[]} msgs - * @param {Omit} [opts] - * @returns {Promise} - base64 encoded bytes string. Can be decoded using the corresponding `Msg*Response` object. - * @throws {Error} if packet fails to send or an error is returned - */ - executeEncodedTx(msgs, opts) { - const { connection } = this.state; - if (!connection) throw Fail`connection not available`; - return E.when( - E(connection).send(makeTxPacket(msgs, opts)), - // if parsePacketAck cannot find a `result` key, it throws - ack => parsePacketAck(ack), - ); - }, - /** - * Close the remote account - */ - async close() { - /// XXX what should the behavior be here? and `onClose`? - // - retrieve assets? - // - revoke the port? - const { connection } = this.state; - 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 - * @param {Brand} brand - */ - async getPurse(brand) { - console.log('getPurse got', brand); - throw new Error('not yet implemented'); - }, - - /* transfer account to new holder */ - async prepareTransfer() { - throw new Error('not yet implemented'); - }, - }, - connectionHandler: { - /** - * @param {Remote} connection - * @param {LocalIbcAddress} localAddr - * @param {RemoteIbcAddress} remoteAddr - */ - async onOpen(connection, localAddr, remoteAddr) { - trace(`ICA Channel Opened for ${localAddr} at ${remoteAddr}`); - this.state.connection = connection; - this.state.remoteAddress = remoteAddr; - this.state.localAddress = localAddr; - // XXX parseAddress currently throws, should it return '' instead? - this.state.chainAddress = harden({ - address: parseAddress(remoteAddr) || UNPARSABLE_CHAIN_ADDRESS, - // TODO get this from `Chain` object #9063 - // XXX how do we get a chainId for an unknown chain? seems it may need to be a user supplied arg - chainId: 'FIXME', - addressEncoding: 'bech32', - }); - trace('got chainAddress', this.state.chainAddress); - trace('parseAddress(remoteAddr)', parseAddress(remoteAddr)); - }, - async onClose(_connection, reason) { - trace(`ICA Channel closed. Reason: ${reason}`); - // XXX handle connection closing - // XXX is there a scenario where a connection will unexpectedly close? _I think yes_ - }, - async onReceive(connection, bytes) { - trace(`ICA Channel onReceive`, connection, bytes); - return ''; - }, - }, - }, - ); - export const OrchestrationI = M.interface('Orchestration', { makeAccount: M.callWhen(M.string(), M.string()).returns( M.remotable('ChainAccount'), @@ -235,9 +52,9 @@ export const OrchestrationI = M.interface('Orchestration', { /** * @param {Zone} zone - * @param {ReturnType} createChainAccount + * @param {ReturnType} makeChainAccountKit */ -const prepareOrchestration = (zone, createChainAccount) => +const prepareOrchestration = (zone, makeChainAccountKit) => zone.exoClassKit( 'Orchestration', { @@ -279,13 +96,16 @@ const prepareOrchestration = (zone, createChainAccount) => hostConnectionId, controllerConnectionId, ); - const chainAccount = createChainAccount(port, remoteConnAddr); + const chainAccountKit = makeChainAccountKit(port, remoteConnAddr); // await so we do not return a ChainAccount before it successfully instantiates - await E(port).connect(remoteConnAddr, chainAccount.connectionHandler); + await E(port).connect( + remoteConnAddr, + chainAccountKit.connectionHandler, + ); // XXX if we fail, should we close the port (if it was created in this flow)? - return chainAccount.account; + return chainAccountKit.account; }, }, }, @@ -293,14 +113,13 @@ const prepareOrchestration = (zone, createChainAccount) => /** @param {Zone} zone */ export const prepareOrchestrationTools = zone => { - const createChainAccount = prepareChainAccount(zone); - const makeOrchestration = prepareOrchestration(zone, createChainAccount); + const makeChainAccountKit = prepareChainAccountKit(zone); + const makeOrchestration = prepareOrchestration(zone, makeChainAccountKit); return harden({ makeOrchestration }); }; harden(prepareOrchestrationTools); -/** @typedef {ReturnType>} ChainAccountKit */ /** @typedef {ReturnType} OrchestrationTools */ /** @typedef {ReturnType} OrchestrationKit */ /** @typedef {OrchestrationKit['public']} OrchestrationService */ diff --git a/packages/orchestration/src/types.d.ts b/packages/orchestration/src/types.d.ts index 5d33dc6a241..bb37e16ff72 100644 --- a/packages/orchestration/src/types.d.ts +++ b/packages/orchestration/src/types.d.ts @@ -4,7 +4,7 @@ import type { Invitation } from '@agoric/zoe/exported.js'; import type { Any } from '@agoric/cosmic-proto/google/protobuf/any'; import type { AnyJson } from '@agoric/cosmic-proto'; import type { - MsgCancelUnbondingDelegation, + MsgBeginRedelegateResponse, MsgUndelegateResponse, } from '@agoric/cosmic-proto/cosmos/staking/v1beta1/tx.js'; import type { @@ -18,6 +18,7 @@ import type { RemoteIbcAddress, } from '@agoric/vats/tools/ibc-utils.js'; import type { Port } from '@agoric/network'; +import { MsgTransferResponse } from '@agoric/cosmic-proto/ibc/applications/transfer/v1/tx.js'; /** * static declaration of known chain types will allow type support for @@ -367,14 +368,14 @@ export interface BaseOrchestrationAccount { srcValidator: CosmosValidatorAddress, dstValidator: CosmosValidatorAddress, amount: AmountArg, - ) => Promise; + ) => Promise; /** * Undelegate multiple delegations (concurrently). To delegate independently, pass an array with one item. * Resolves when the undelegation is complete and the tokens are no longer bonded. Note it may take weeks. * @param {Delegation[]} delegations - the delegation to undelegate */ - undelegate: (delegations: Delegation[]) => Promise; + undelegate: (delegations: Delegation[]) => Promise; /** * Withdraw rewards from all validators. The promise settles when the rewards are withdrawn. @@ -403,7 +404,7 @@ export interface BaseOrchestrationAccount { amount: AmountArg, destination: ChainAddress, memo?: string, - ) => Promise; + ) => Promise; /** * Transfer an amount to another account in multiple steps. The promise settles when @@ -412,7 +413,10 @@ export interface BaseOrchestrationAccount { * @param msg - the transfer message, including follow-up steps * @returns void */ - transferSteps: (amount: AmountArg, msg: TransferMsg) => Promise; + transferSteps: ( + amount: AmountArg, + msg: TransferMsg, + ) => Promise; /** * deposit payment from zoe to the account. For remote accounts, * an IBC Transfer will be executed to transfer funds there.