diff --git a/packages/boot/test/bootstrapTests/orchestration.test.ts b/packages/boot/test/bootstrapTests/orchestration.test.ts index d5fc69792bb..4bdc1fc7f4b 100644 --- a/packages/boot/test/bootstrapTests/orchestration.test.ts +++ b/packages/boot/test/bootstrapTests/orchestration.test.ts @@ -72,6 +72,42 @@ test.serial('config', async t => { } }); +test.serial('stakeOsmo - queries', async t => { + const { + buildProposal, + evalProposal, + runUtils: { EV }, + } = t.context; + await evalProposal( + buildProposal('@agoric/builders/scripts/orchestration/init-stakeOsmo.js'), + ); + + const agoricNames = await EV.vat('bootstrap').consumeItem('agoricNames'); + const instance: Instance = await EV(agoricNames).lookup( + 'instance', + 'stakeOsmo', + ); + t.truthy(instance, 'stakeOsmo instance is available'); + + const zoe: ZoeService = await EV.vat('bootstrap').consumeItem('zoe'); + const publicFacet = await EV(zoe).getPublicFacet(instance); + t.truthy(publicFacet, 'stakeOsmo publicFacet is available'); + + const account = await EV(publicFacet).makeAccount(); + t.log('account', account); + t.truthy(account, 'makeAccount returns an account on OSMO connection'); + + const queryRes = await EV(account).getBalance('uatom'); + t.deepEqual(queryRes, { value: 0n, denom: 'uatom' }); + + const queryUnknownDenom = await EV(account).getBalance('some-invalid-denom'); + t.deepEqual( + queryUnknownDenom, + { value: 0n, denom: 'some-invalid-denom' }, + 'getBalance for unknown denom returns value: 0n', + ); +}); + test.serial('stakeAtom - repl-style', async t => { const { buildProposal, @@ -111,19 +147,13 @@ test.serial('stakeAtom - repl-style', async t => { }; await t.notThrowsAsync(EV(account).delegate(validatorAddress, atomAmount)); - const queryRes = await EV(account).getBalance('uatom'); - t.deepEqual(queryRes, { value: 0n, denom: 'uatom' }); - - const queryUnknownDenom = await EV(account).getBalance('some-invalid-denom'); - t.deepEqual( - queryUnknownDenom, - { value: 0n, denom: 'some-invalid-denom' }, - 'getBalance for unknown denom returns value: 0n', - ); + await t.throwsAsync(EV(account).getBalance('uatom'), { + message: 'Queries not available for chain "cosmoshub-4"', + }); }); test.serial('stakeAtom - smart wallet', async t => { - const { agoricNamesRemotes } = t.context; + const { agoricNamesRemotes, readLatest } = t.context; const wd = await t.context.walletFactoryDriver.provideSmartWallet( 'agoric1testStakAtom', @@ -140,12 +170,18 @@ test.serial('stakeAtom - smart wallet', async t => { }); t.like(wd.getCurrentWalletRecord(), { offerToPublicSubscriberPaths: [ - ['request-account', { account: 'published.stakeAtom' }], + [ + 'request-account', + { + account: 'published.stakeAtom.accounts.cosmos1test', + }, + ], ], }); t.like(wd.getLatestUpdateRecord(), { status: { id: 'request-account', numWantsSatisfied: 1 }, }); + t.is(readLatest('published.stakeAtom.accounts.cosmos1test'), ''); const { ATOM } = agoricNamesRemotes.brand; ATOM || Fail`ATOM missing from agoricNames`; diff --git a/packages/builders/scripts/orchestration/init-stakeAtom.js b/packages/builders/scripts/orchestration/init-stakeAtom.js index 68166a7601d..da1ea661073 100644 --- a/packages/builders/scripts/orchestration/init-stakeAtom.js +++ b/packages/builders/scripts/orchestration/init-stakeAtom.js @@ -1,15 +1,7 @@ import { makeHelpers } from '@agoric/deploy-script-support'; /** @type {import('@agoric/deploy-script-support/src/externalTypes.js').CoreEvalBuilder} */ -export const defaultProposalBuilder = async ( - { publishRef, install }, - options = {}, -) => { - const { - hostConnectionId = 'connection-1', - controllerConnectionId = 'connection-0', - bondDenom = 'uatom', - } = options; +export const defaultProposalBuilder = async ({ publishRef, install }) => { return harden({ sourceSpec: '@agoric/orchestration/src/proposals/start-stakeAtom.js', getManifestCall: [ @@ -20,9 +12,6 @@ export const defaultProposalBuilder = async ( install('@agoric/orchestration/src/examples/stakeIca.contract.js'), ), }, - hostConnectionId, - controllerConnectionId, - bondDenom, }, ], }); diff --git a/packages/builders/scripts/orchestration/init-stakeOsmo.js b/packages/builders/scripts/orchestration/init-stakeOsmo.js new file mode 100644 index 00000000000..7374d3d59a6 --- /dev/null +++ b/packages/builders/scripts/orchestration/init-stakeOsmo.js @@ -0,0 +1,23 @@ +import { makeHelpers } from '@agoric/deploy-script-support'; + +/** @type {import('@agoric/deploy-script-support/src/externalTypes.js').CoreEvalBuilder} */ +export const defaultProposalBuilder = async ({ publishRef, install }) => { + return harden({ + sourceSpec: '@agoric/orchestration/src/proposals/start-stakeOsmo.js', + getManifestCall: [ + 'getManifestForStakeOsmo', + { + installKeys: { + stakeIca: publishRef( + install('@agoric/orchestration/src/examples/stakeIca.contract.js'), + ), + }, + }, + ], + }); +}; + +export default async (homeP, endowments) => { + const { writeCoreEval } = await makeHelpers(homeP, endowments); + await writeCoreEval('start-stakeOsmo', defaultProposalBuilder); +}; diff --git a/packages/orchestration/src/cosmos-api.ts b/packages/orchestration/src/cosmos-api.ts index 2eefdd7b9f8..08a95916ccd 100644 --- a/packages/orchestration/src/cosmos-api.ts +++ b/packages/orchestration/src/cosmos-api.ts @@ -151,7 +151,9 @@ export interface StakingAccountActions { * The unbonding time is padded by 10 minutes to account for clock skew. * @param delegations - the delegation to undelegate */ - undelegate: (delegations: Delegation[]) => Promise; + undelegate: ( + delegations: Omit[], + ) => Promise; /** * Withdraw rewards from all validators. The promise settles when the rewards are withdrawn. diff --git a/packages/orchestration/src/examples/sendAnywhere.contract.js b/packages/orchestration/src/examples/sendAnywhere.contract.js index ed49472cee2..301f6da3f2d 100644 --- a/packages/orchestration/src/examples/sendAnywhere.contract.js +++ b/packages/orchestration/src/examples/sendAnywhere.contract.js @@ -68,6 +68,7 @@ export const start = async (zcf, privateArgs, baggage) => { zone, chainHub, makeLocalChainAccountKit, + makeRecorderKit, ...orchPowers, }); diff --git a/packages/orchestration/src/examples/stakeIca.contract.js b/packages/orchestration/src/examples/stakeIca.contract.js index 8f4562220a4..40fc24c08e1 100644 --- a/packages/orchestration/src/examples/stakeIca.contract.js +++ b/packages/orchestration/src/examples/stakeIca.contract.js @@ -3,7 +3,10 @@ import { makeTracer, StorageNodeShape } from '@agoric/internal'; import { TimerServiceShape } from '@agoric/time'; import { V as E } from '@agoric/vow/vat.js'; -import { prepareRecorderKitMakers } from '@agoric/zoe/src/contractSupport'; +import { + prepareRecorderKitMakers, + provideAll, +} 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'; @@ -24,6 +27,7 @@ export const meta = harden({ hostConnectionId: M.string(), controllerConnectionId: M.string(), bondDenom: M.string(), + icqEnabled: M.boolean(), }, privateArgsShape: { orchestration: M.remotable('orchestration'), @@ -40,6 +44,7 @@ export const privateArgsShape = meta.privateArgsShape; * hostConnectionId: IBCConnectionID; * controllerConnectionId: IBCConnectionID; * bondDenom: string; + * icqEnabled: boolean; * }} StakeIcaTerms */ @@ -54,12 +59,21 @@ export const privateArgsShape = meta.privateArgsShape; * @param {Baggage} baggage */ export const start = async (zcf, privateArgs, baggage) => { - const { chainId, hostConnectionId, controllerConnectionId, bondDenom } = - zcf.getTerms(); + const { + chainId, + hostConnectionId, + controllerConnectionId, + bondDenom, + icqEnabled, + } = zcf.getTerms(); const { orchestration, marshaller, storageNode, timer } = privateArgs; const zone = makeDurableZone(baggage); + const { accountsStorageNode } = await provideAll(baggage, { + accountsStorageNode: () => E(storageNode).makeChildNode('accounts'), + }); + const { makeRecorderKit } = prepareRecorderKitMakers(baggage, marshaller); const makeCosmosOrchestrationAccount = prepareCosmosOrchestrationAccount( @@ -74,17 +88,19 @@ export const start = async (zcf, privateArgs, baggage) => { hostConnectionId, controllerConnectionId, ); - // 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, - ); + // TODO permissionless queries https://github.com/Agoric/agoric-sdk/issues/9326 + const icqConnection = icqEnabled + ? await E(orchestration).provideICQConnection(controllerConnectionId) + : undefined; + const accountAddress = await E(account).getAddress(); trace('account address', accountAddress); + const accountNode = await E(accountsStorageNode).makeChildNode( + accountAddress.address, + ); const holder = makeCosmosOrchestrationAccount(accountAddress, bondDenom, { account, - storageNode, + storageNode: accountNode, icqConnection, timer, }); @@ -121,4 +137,4 @@ export const start = async (zcf, privateArgs, baggage) => { return { publicFacet }; }; -/** @typedef {typeof start} StakeAtomSF */ +/** @typedef {typeof start} StakeIcaSF */ diff --git a/packages/orchestration/src/examples/swapExample.contract.js b/packages/orchestration/src/examples/swapExample.contract.js index a7ba4c8db2e..9b36e37d91a 100644 --- a/packages/orchestration/src/examples/swapExample.contract.js +++ b/packages/orchestration/src/examples/swapExample.contract.js @@ -2,9 +2,10 @@ 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 { E, Far } from '@endo/far'; import { deeplyFulfilled } from '@endo/marshal'; import { M, objectMap } from '@endo/patterns'; +import { provideAll } from '@agoric/zoe/src/contractSupport'; import { prepareRecorderKitMakers } from '@agoric/zoe/src/contractSupport/recorder.js'; import { makeOrchestrationFacade } from '../facade.js'; import { orcUtils } from '../utils/orc.js'; @@ -79,16 +80,20 @@ export const start = async (zcf, privateArgs, baggage) => { timerService, chainHub, ); + const { accountsStorageNode } = await provideAll(baggage, { + accountsStorageNode: () => E(storageNode).makeChildNode('accounts'), + }); const { orchestrate } = makeOrchestrationFacade({ localchain, orchestrationService, - storageNode, + storageNode: accountsStorageNode, timerService, zcf, zone, chainHub, makeLocalChainAccountKit, + makeRecorderKit, }); /** deprecated historical example */ diff --git a/packages/orchestration/src/examples/unbondExample.contract.js b/packages/orchestration/src/examples/unbondExample.contract.js index 7bd5d2053e6..8f707e2f4ac 100644 --- a/packages/orchestration/src/examples/unbondExample.contract.js +++ b/packages/orchestration/src/examples/unbondExample.contract.js @@ -57,6 +57,7 @@ export const start = async (zcf, privateArgs, baggage) => { zone, chainHub: makeChainHub(agoricNames), makeLocalChainAccountKit, + makeRecorderKit, }); /** @type {OfferHandler} */ diff --git a/packages/orchestration/src/exos/chainAccountKit.js b/packages/orchestration/src/exos/chainAccountKit.js index 35d20c5583d..187b661ea8e 100644 --- a/packages/orchestration/src/exos/chainAccountKit.js +++ b/packages/orchestration/src/exos/chainAccountKit.js @@ -160,6 +160,8 @@ export const prepareChainAccountKit = zone => this.state.remoteAddress = remoteAddr; this.state.localAddress = localAddr; this.state.chainAddress = harden({ + // FIXME need a fallback value like icacontroller-1-connection-1 if this fails + // https://github.com/Agoric/agoric-sdk/issues/9066 address: findAddressField(remoteAddr) || UNPARSABLE_CHAIN_ADDRESS, chainId: this.state.chainId, addressEncoding: 'bech32', diff --git a/packages/orchestration/src/exos/cosmosOrchestrationAccount.js b/packages/orchestration/src/exos/cosmosOrchestrationAccount.js index f1c10cb24ac..529f35c77d4 100644 --- a/packages/orchestration/src/exos/cosmosOrchestrationAccount.js +++ b/packages/orchestration/src/exos/cosmosOrchestrationAccount.js @@ -16,7 +16,6 @@ import { MsgUndelegateResponse, } from '@agoric/cosmic-proto/cosmos/staking/v1beta1/tx.js'; import { Any } from '@agoric/cosmic-proto/google/protobuf/any.js'; -import { AmountShape } from '@agoric/ertp'; import { makeTracer } from '@agoric/internal'; import { M } from '@agoric/vat-data'; import { TopicsRecordShape } from '@agoric/zoe/src/contractSupport/index.js'; @@ -60,7 +59,7 @@ const { Fail } = assert; * topicKit: RecorderKit; * account: IcaAccount; * chainAddress: ChainAddress; - * icqConnection: ICQConnection; + * icqConnection: ICQConnection | undefined; * bondDenom: string; * timer: Remote; * }} State @@ -75,11 +74,13 @@ export const IcaAccountHolderI = M.interface('IcaAccountHolder', { holder: M.any(), }), getPublicTopics: M.call().returns(TopicsRecordShape), - delegate: M.callWhen(ChainAddressShape, AmountShape).returns(M.undefined()), + delegate: M.callWhen(ChainAddressShape, AmountArgShape).returns( + M.undefined(), + ), redelegate: M.callWhen( ChainAddressShape, ChainAddressShape, - AmountShape, + AmountArgShape, ).returns(M.undefined()), withdrawReward: M.callWhen(ChainAddressShape).returns( M.arrayOf(ChainAmountShape), @@ -123,11 +124,11 @@ export const prepareCosmosOrchestrationAccountKit = ( helper: M.interface('helper', { owned: M.call().returns(M.remotable()), getUpdater: M.call().returns(M.remotable()), - amountToCoin: M.call(AmountShape).returns(M.record()), + amountToCoin: M.call(AmountArgShape).returns(M.record()), }), holder: IcaAccountHolderI, invitationMakers: M.interface('invitationMakers', { - Delegate: M.callWhen(ChainAddressShape, AmountShape).returns( + Delegate: M.callWhen(ChainAddressShape, AmountArgShape).returns( InvitationShape, ), Redelegate: M.callWhen( @@ -149,7 +150,7 @@ export const prepareCosmosOrchestrationAccountKit = ( * @param {object} io * @param {IcaAccount} io.account * @param {Remote} io.storageNode - * @param {ICQConnection} io.icqConnection + * @param {ICQConnection | undefined} io.icqConnection * @param {Remote} io.timer * @returns {State} */ @@ -158,6 +159,8 @@ export const prepareCosmosOrchestrationAccountKit = ( // must be the fully synchronous maker because the kit is held in durable state // @ts-expect-error XXX Patterns const topicKit = makeRecorderKit(storageNode, PUBLIC_TOPICS.account[1]); + // TODO determine what goes in vstorage https://github.com/Agoric/agoric-sdk/issues/9066 + void E(topicKit.recorder).write(''); return { chainAddress, bondDenom, topicKit, ...rest }; }, @@ -195,7 +198,7 @@ export const prepareCosmosOrchestrationAccountKit = ( invitationMakers: { /** * @param {CosmosValidatorAddress} validator - * @param {Amount<'nat'>} amount + * @param {AmountArg} amount */ Delegate(validator, amount) { trace('Delegate', validator, amount); @@ -231,7 +234,7 @@ export const prepareCosmosOrchestrationAccountKit = ( return this.facets.holder.withdrawReward(validator); }, 'WithdrawReward'); }, - /** @param {Delegation[]} delegations */ + /** @param {Omit[]} delegations */ Undelegate(delegations) { trace('Undelegate', delegations); @@ -363,6 +366,9 @@ export const prepareCosmosOrchestrationAccountKit = ( */ async getBalance(denom) { const { chainAddress, icqConnection } = this.state; + if (!icqConnection) { + throw Fail`Queries not available for chain ${chainAddress.chainId}`; + } // TODO #9211 lookup denom from brand assert.typeof(denom, 'string'); @@ -401,7 +407,7 @@ export const prepareCosmosOrchestrationAccountKit = ( throw assert.error('Not implemented'); }, - /** @param {Delegation[]} delegations */ + /** @param {Omit[]} delegations */ async undelegate(delegations) { trace('undelegate', delegations); const { helper } = this.facets; diff --git a/packages/orchestration/src/facade.js b/packages/orchestration/src/facade.js index b2e3beb3384..9567b974db9 100644 --- a/packages/orchestration/src/facade.js +++ b/packages/orchestration/src/facade.js @@ -8,6 +8,7 @@ import { prepareCosmosOrchestrationAccount } from './exos/cosmosOrchestrationAcc * @import {TimerService} from '@agoric/time'; * @import {IBCConnectionID} from '@agoric/vats'; * @import {LocalChain} from '@agoric/vats/src/localchain.js'; + * @import {RecorderKit, MakeRecorderKit} from '@agoric/zoe/src/contractSupport/recorder.js'. * @import {Remote} from '@agoric/internal'; * @import {OrchestrationService} from './service.js'; * @import {Chain, ChainInfo, CosmosChainInfo, IBCConnectionInfo, OrchestrationAccount, Orchestrator} from './types.js'; @@ -95,6 +96,8 @@ const makeLocalChainFacade = ( * @param {IBCConnectionInfo} connectionInfo * @param {object} io * @param {Remote} io.orchestration + * @param {MakeRecorderKit} io.makeRecorderKit + * @param {Remote} io.storageNode * @param {Remote} io.timer * @param {ZCF} io.zcf * @param {Zone} io.zone @@ -103,9 +106,8 @@ const makeLocalChainFacade = ( const makeRemoteChainFacade = ( chainInfo, connectionInfo, - { orchestration, timer, zcf, zone }, + { orchestration, makeRecorderKit, storageNode, timer, zcf, zone }, ) => { - const makeRecorderKit = () => anyVal; const makeCosmosOrchestrationAccount = prepareCosmosOrchestrationAccount( zone.subZone(chainInfo.chainId), makeRecorderKit, @@ -133,7 +135,7 @@ const makeRemoteChainFacade = ( // @ts-expect-error XXX dynamic method availability return makeCosmosOrchestrationAccount(address, bondDenom, { account: icaAccount, - storageNode: anyVal, + storageNode, icqConnection: anyVal, timer, }); @@ -153,6 +155,7 @@ const makeRemoteChainFacade = ( * makeLocalChainAccountKit: ReturnType< * typeof import('./exos/local-chain-account-kit.js').prepareLocalChainAccountKit * >; + * makeRecorderKit: MakeRecorderKit; * }} powers */ export const makeOrchestrationFacade = ({ @@ -164,6 +167,7 @@ export const makeOrchestrationFacade = ({ localchain, chainHub, makeLocalChainAccountKit, + makeRecorderKit, }) => { console.log('makeOrchestrationFacade got', { zone, @@ -205,6 +209,8 @@ export const makeOrchestrationFacade = ({ return makeRemoteChainFacade(remoteChainInfo, connectionInfo, { orchestration: orchestrationService, + makeRecorderKit, + storageNode, timer: timerService, zcf, zone, diff --git a/packages/orchestration/src/proposals/start-stakeAtom.js b/packages/orchestration/src/proposals/start-stakeAtom.js index 228b4ccc9bc..f0150404470 100644 --- a/packages/orchestration/src/proposals/start-stakeAtom.js +++ b/packages/orchestration/src/proposals/start-stakeAtom.js @@ -5,7 +5,7 @@ import { makeChainHub } from '../utils/chainHub.js'; /** * @import {IBCConnectionID} from '@agoric/vats'; - * @import {StakeAtomSF, StakeIcaTerms} from '../examples/stakeIca.contract'; + * @import {StakeIcaSF, StakeIcaTerms} from '../examples/stakeIca.contract'; */ const trace = makeTracer('StartStakeAtom', true); @@ -43,8 +43,6 @@ export const startStakeAtom = async ({ const storageNode = await makeStorageNodeChild(chainStorage, VSTORAGE_PATH); const marshaller = await E(board).getPublishingMarshaller(); - const atomIssuer = await E(agoricNames).lookup('issuer', 'ATOM'); - trace('ATOM Issuer', atomIssuer); const chainHub = makeChainHub(await agoricNames); @@ -55,16 +53,16 @@ export const startStakeAtom = async ({ cosmoshub.chainId, ); - /** @type {StartUpgradableOpts} */ + /** @type {StartUpgradableOpts} */ const startOpts = { label: 'stakeAtom', installation: stakeIca, - issuerKeywordRecord: harden({ ATOM: atomIssuer }), terms: { chainId: cosmoshub.chainId, hostConnectionId: connectionInfo.id, controllerConnectionId: connectionInfo.counterparty.connection_id, bondDenom: cosmoshub.stakingTokens[0].denom, + icqEnabled: cosmoshub.icqEnabled, }, privateArgs: { orchestration: await orchestration, diff --git a/packages/orchestration/src/proposals/start-stakeOsmo.js b/packages/orchestration/src/proposals/start-stakeOsmo.js new file mode 100644 index 00000000000..96c8ea69fbe --- /dev/null +++ b/packages/orchestration/src/proposals/start-stakeOsmo.js @@ -0,0 +1,109 @@ +import { makeTracer } from '@agoric/internal'; +import { makeStorageNodeChild } from '@agoric/internal/src/lib-chainStorage.js'; +import { E } from '@endo/far'; +import { makeChainHub } from '../utils/chainHub.js'; + +/** + * @import {IBCConnectionID} from '@agoric/vats'; + * @import {StakeIcaSF} from '../examples/stakeIca.contract'; + */ + +const trace = makeTracer('StartStakeOsmo', true); + +/** + * @param {BootstrapPowers & { + * installation: { + * consume: { + * stakeIca: Installation< + * import('../examples/stakeIca.contract.js').start + * >; + * }; + * }; + * }} powers + */ +export const startStakeOsmo = async ({ + consume: { + agoricNames, + board, + chainStorage, + chainTimerService, + orchestration, + startUpgradable, + }, + installation: { + consume: { stakeIca }, + }, + instance: { + // @ts-expect-error stakeOsmo not typed + produce: { stakeOsmo: produceInstance }, + }, +}) => { + const VSTORAGE_PATH = 'stakeOsmo'; + trace('startStakeOsmo'); + await null; + + const storageNode = await makeStorageNodeChild(chainStorage, VSTORAGE_PATH); + const marshaller = await E(board).getPublishingMarshaller(); + + const chainHub = makeChainHub(await agoricNames); + + const agoric = await chainHub.getChainInfo('agoric'); + const osmosis = await chainHub.getChainInfo('osmosis'); + const connectionInfo = await chainHub.getConnectionInfo( + agoric.chainId, + osmosis.chainId, + ); + + /** @type {StartUpgradableOpts} */ + const startOpts = { + label: 'stakeOsmo', + installation: stakeIca, + terms: { + chainId: osmosis.chainId, + hostConnectionId: connectionInfo.id, + controllerConnectionId: connectionInfo.counterparty.connection_id, + bondDenom: osmosis.stakingTokens[0].denom, + icqEnabled: osmosis.icqEnabled, + }, + privateArgs: { + orchestration: await orchestration, + storageNode, + marshaller, + timer: await chainTimerService, + }, + }; + + const { instance } = await E(startUpgradable)(startOpts); + produceInstance.resolve(instance); +}; +harden(startStakeOsmo); + +export const getManifestForStakeOsmo = ( + { restoreRef }, + { installKeys, ...options }, +) => { + return { + manifest: { + [startStakeOsmo.name]: { + consume: { + agoricNames: true, + board: true, + chainStorage: true, + chainTimerService: true, + orchestration: true, + startUpgradable: true, + }, + installation: { + consume: { stakeIca: true }, + }, + instance: { + produce: { stakeOsmo: true }, + }, + }, + }, + installations: { + stakeIca: restoreRef(installKeys.stakeIca), + }, + options, + }; +}; diff --git a/packages/orchestration/src/typeGuards.js b/packages/orchestration/src/typeGuards.js index 6cc02a4dbad..e3a836cf2dc 100644 --- a/packages/orchestration/src/typeGuards.js +++ b/packages/orchestration/src/typeGuards.js @@ -25,7 +25,6 @@ export const ChainAmountShape = harden({ denom: M.string(), value: M.nat() }); export const AmountArgShape = M.or(AmountShape, ChainAmountShape); export const DelegationShape = harden({ - delegatorAddress: M.string(), validatorAddress: M.string(), shares: M.string(), // TODO: bigint? }); diff --git a/packages/orchestration/test/examples/stake-atom.contract.test.ts b/packages/orchestration/test/examples/stake-atom.contract.test.ts index 31f8478b03e..5df7dc624de 100644 --- a/packages/orchestration/test/examples/stake-atom.contract.test.ts +++ b/packages/orchestration/test/examples/stake-atom.contract.test.ts @@ -1,11 +1,12 @@ 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 { makeNotifierFromSubscriber } from '@agoric/notifier'; import type { Installation } from '@agoric/zoe/src/zoeService/utils.js'; import { commonSetup } from '../supports.js'; +import { type StakeIcaTerms } from '../../src/examples/stakeIca.contract.js'; const dirname = path.dirname(new URL(import.meta.url).pathname); @@ -18,7 +19,14 @@ const startContract = async ({ timer, marshaller, storage, - bld, + issuerKeywordRecord = undefined, + terms = { + chainId: 'cosmoshub-4', + hostConnectionId: 'connection-1', + controllerConnectionId: 'connection-2', + bondDenom: 'uatom', + icqEnabled: false, + } as StakeIcaTerms, }) => { const { zoe, bundleAndInstall } = await setUpZoeForTest(); const installation: Installation = @@ -26,38 +34,33 @@ const startContract = async ({ const { publicFacet } = await E(zoe).startInstance( installation, - { In: bld.issuer }, - { - chainId: 'cosmoshub-4', - hostConnectionId: 'connection-1', - controllerConnectionId: 'connection-2', - bondDenom: 'uatom', - }, + issuerKeywordRecord, + terms, { marshaller, orchestration, - storageNode: storage.rootNode, + storageNode: storage.rootNode.makeChildNode('stakeAtom'), timer, }, ); return { publicFacet, zoe }; }; -test('makeAccount, deposit, withdraw', async t => { +test('makeAccount, getAddress, getBalances, getBalance', async t => { const { bootstrap, brands: { ist }, utils, } = await commonSetup(t); - const { publicFacet } = await startContract({ ...bootstrap, bld: ist }); + const { publicFacet } = await startContract(bootstrap); 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: 'cosmoshub-4', addressEncoding: 'bech32' }); + const chainAddress = await E(account).getAddress(); + // FIXME mock remoteAddress in ibc bridge. Currently UNPARSABLE_CHAIN_ADDRESS + // t.regex(address.address, /cosmos1/); + t.like(chainAddress, { chainId: 'cosmoshub-4', addressEncoding: 'bech32' }); t.log('deposit 100 bld to account'); await E(account).deposit(await utils.pourPayment(ist.units(100))); @@ -65,4 +68,43 @@ test('makeAccount, deposit, withdraw', async t => { await t.throwsAsync(E(account).getBalances(), { message: 'not yet implemented', }); + + await t.throwsAsync(E(account).getBalance('uatom'), { + message: 'Queries not available for chain "cosmoshub-4"', + }); +}); + +test('makeAccountInvitationMaker', async t => { + const { bootstrap } = await commonSetup(t); + const { publicFacet, zoe } = await startContract(bootstrap); + const inv = await E(publicFacet).makeAccountInvitationMaker(); + t.log('make an offer for ICA account'); + + const seat = await E(zoe).offer(inv); + const offerResult = await E(seat).getOfferResult(); + + t.like(offerResult, { + publicSubscribers: { + account: { + description: 'Staking Account holder status', + }, + }, + }); + + const accountNotifier = makeNotifierFromSubscriber( + offerResult.publicSubscribers.account.subscriber, + ); + const storageUpdate = await E(accountNotifier).getUpdateSince(); + t.deepEqual(storageUpdate, { + updateCount: 1n, + value: '', + }); + + // FIXME mock remoteAddress in ibc bridge + const storagePath = + 'mockChainStorageRoot.stakeAtom.accounts.UNPARSABLE_CHAIN_ADDRESS'; + const vstorageEntry = bootstrap.storage.data.get(storagePath); + t.truthy(vstorageEntry, 'vstorage account entry created'); + t.log(storagePath, vstorageEntry); + t.is(bootstrap.marshaller.fromCapData(JSON.parse(vstorageEntry!)), ''); }); diff --git a/packages/orchestration/test/facade.test.ts b/packages/orchestration/test/facade.test.ts index 28ede33b678..e68a3cdd54f 100644 --- a/packages/orchestration/test/facade.test.ts +++ b/packages/orchestration/test/facade.test.ts @@ -1,6 +1,7 @@ import { test as anyTest } from '@agoric/zoe/tools/prepare-test-env-ava.js'; import { setupZCFTest } from '@agoric/zoe/test/unitTests/zcf/setupZcfTest.js'; +import { prepareRecorderKitMakers } from '@agoric/zoe/src/contractSupport/recorder.js'; import type { CosmosChainInfo, IBCConnectionInfo } from '../src/cosmos-api.js'; import { makeOrchestrationFacade } from '../src/facade.js'; import type { Chain } from '../src/orchestration-api.js'; @@ -48,6 +49,10 @@ test('chain info', async t => { const { zcf } = await setupZCFTest(); const chainHub = makeChainHub(facadeServices.agoricNames); + const { makeRecorderKit } = prepareRecorderKitMakers( + zone.mapStore('recorder'), + bootstrap.marshaller, + ); const { orchestrate } = makeOrchestrationFacade({ ...facadeServices, @@ -56,6 +61,7 @@ test('chain info', async t => { zone, chainHub, makeLocalChainAccountKit, + makeRecorderKit, }); chainHub.registerChain('mock', mockChainInfo); diff --git a/packages/orchestration/test/staking-ops.test.ts b/packages/orchestration/test/staking-ops.test.ts index 4059ac372f8..f6eb1bd8e9a 100644 --- a/packages/orchestration/test/staking-ops.test.ts +++ b/packages/orchestration/test/staking-ops.test.ts @@ -8,6 +8,7 @@ import { MsgUndelegateResponse, } from '@agoric/cosmic-proto/cosmos/staking/v1beta1/tx.js'; import { makeScalarBigMapStore, type Baggage } from '@agoric/vat-data'; +import { makeFakeBoard } from '@agoric/vats/tools/board-utils.js'; import { decodeBase64 } from '@endo/base64'; import { E, Far } from '@endo/far'; import { buildZoeManualTimer } from '@agoric/zoe/tools/manualTimer.js'; @@ -16,6 +17,9 @@ import type { Coin } from '@agoric/cosmic-proto/cosmos/base/v1beta1/coin.js'; import type { TimestampRecord, TimestampValue } from '@agoric/time'; import type { AnyJson } from '@agoric/cosmic-proto'; import { makeDurableZone } from '@agoric/zone/durable.js'; +import { makeFakeStorageKit } from '@agoric/internal/src/storage-test-utils.js'; +import { makeNotifierFromSubscriber } from '@agoric/notifier'; +import { prepareRecorderKitMakers } from '@agoric/zoe/src/contractSupport/recorder.js'; import { prepareCosmosOrchestrationAccountKit, trivialDelegateResponse, @@ -174,17 +178,16 @@ const makeScenario = () => { return { zcf, zoe }; }; - const makeRecorderKit = () => harden({}) as any; - const baggage = makeScalarBigMapStore('b1') as Baggage; const zone = makeDurableZone(baggage); + const marshaller = makeFakeBoard().getReadonlyMarshaller(); + const { makeRecorderKit } = prepareRecorderKitMakers(baggage, marshaller); const { delegations, startTime } = configStaking; - // TODO: when we write to chainStorage, test it. - // const { rootNode } = makeFakeStorageKit('mockChainStorageRoot'); - - const storageNode = Far('StorageNode', {}) as unknown as StorageNode; + const { rootNode } = makeFakeStorageKit('stakingOpsTest', { + sequence: false, + }); const icqConnection = Far('ICQConnection', {}) as ICQConnection; @@ -197,13 +200,36 @@ const makeScenario = () => { zone, makeRecorderKit, ...mockAccount(undefined, delegations), - storageNode, + storageNode: rootNode, timer, icqConnection, ...mockZCF(), }; }; +test('makeAccount() writes to storage', async t => { + const s = makeScenario(); + const { account, timer } = s; + const { makeRecorderKit, storageNode, zcf, icqConnection, zone } = s; + const make = prepareCosmosOrchestrationAccountKit(zone, makeRecorderKit, zcf); + + const { holder } = make(account.getAddress(), 'uatom', { + account, + storageNode, + icqConnection, + timer, + }); + const { publicSubscribers } = holder.asContinuingOffer(); + const accountNotifier = makeNotifierFromSubscriber( + publicSubscribers.account.subscriber, + ); + const storageUpdate = await E(accountNotifier).getUpdateSince(); + t.deepEqual(storageUpdate, { + updateCount: 1n, + value: '', + }); +}); + test('withdrawRewards() on StakingAccountHolder formats message correctly', async t => { const s = makeScenario(); const { account, calls, timer } = s; @@ -248,8 +274,11 @@ test(`delegate; redelegate using invitationMakers`, async t => { const { validator, delegations } = configStaking; { const value = BigInt(Object.values(delegations)[0].amount); - const anAmount = { brand: aBrand, value }; - const toDelegate = await E(invitationMakers).Delegate(validator, anAmount); + const anAmountArg = { denom: 'uatom', value }; + const toDelegate = await E(invitationMakers).Delegate( + validator, + anAmountArg, + ); const seat = E(zoe).offer(toDelegate); const result = await E(seat).getOfferResult(); @@ -349,7 +378,6 @@ test(`undelegate waits for unbonding period`, async t => { const value = BigInt(Object.values(delegations)[0].amount); const anAmount = { brand: Far('Token'), value } as Amount<'nat'>; const delegation = { - delegatorAddress: account.getAddress().address, shares: `${anAmount.value}`, validatorAddress: validator.address, }; diff --git a/packages/vm-config/decentral-devnet-config.json b/packages/vm-config/decentral-devnet-config.json index e93ba27a609..232420684cc 100644 --- a/packages/vm-config/decentral-devnet-config.json +++ b/packages/vm-config/decentral-devnet-config.json @@ -165,19 +165,6 @@ } ] } - ], - [ - { - "module": "@agoric/builders/scripts/orchestration/init-stakeAtom.js", - "entrypoint": "defaultProposalBuilder", - "args": [ - { - "hostConnectionId": "connection-1", - "controllerConnectionId": "connection-0", - "bondDenom": "uatom" - } - ] - } ] ] },