From 9527b470bb3339dd6893a202dcde2a4f5a02761c Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Fri, 20 Sep 2024 09:50:57 -0500 Subject: [PATCH] test(orca-contract): never mind vstorage layer makeQueryToolMock() was disconnected from the behavior of the contract - use vt.when() to resolve the offer result vow - test passStyleOf invitationMakers - test some details of publicSubscribers - use bootstrap powers from boot-tools.js for agoricNames, storage, marshaller, zoe, bldIssuerKit, ... - use registerChain to add chains to agoricNames - prune getChainHub test on cosmosInterchainService It has no such method. - make cosmosInterchainService, localchain mocks conform to production API - clean up logging a bit - removed test for bad chainName - no need to test this test code --- contract/test/orca-contract.test.js | 590 ++++++++++------------------ 1 file changed, 203 insertions(+), 387 deletions(-) diff --git a/contract/test/orca-contract.test.js b/contract/test/orca-contract.test.js index 36d5706..4b3f72e 100644 --- a/contract/test/orca-contract.test.js +++ b/contract/test/orca-contract.test.js @@ -4,19 +4,24 @@ import { test as anyTest } from './prepare-test-env-ava.js'; import { createRequire } from 'module'; -import { E, Far } from '@endo/far'; -// import { makeCopyBag } from '@endo/patterns'; +import { E, Far, passStyleOf } from '@endo/far'; import { makeNodeBundleCache } from '@endo/bundle-source/cache.js'; -import { makeZoeKitForTest } from '@agoric/zoe/tools/setup-zoe.js'; -import { AmountMath, makeIssuerKit } from '@agoric/ertp'; +import { AmountMath } from '@agoric/ertp'; +import { registerChain } from '@agoric/orchestration/src/chain-info.js'; +import { prepareVowTools } from '@agoric/vow/vat.js'; import { startOrcaContract } from '../src/orca.proposal.js'; -import { makeMockTools } from './boot-tools.js'; +import { makeMockTools, mockBootstrapPowers } from './boot-tools.js'; import { getBundleId } from '../tools/bundle-tools.js'; import { startOrchCoreEval } from '../tools/startOrch.js'; /** @typedef {typeof import('../src/orca.contract.js').start} OrcaContractFn */ +/** + * @import {ChainInfo, IBCConnectionInfo, IcaAccount, MakeCosmosInterchainService} from '@agoric/orchestration'; + * @import {LocalChain,LocalChainAccount} from '@agoric/vats/src/localchain.js'; + * @import {TargetRegistration} from '@agoric/vats/src/bridge-target.js'; + */ const myRequire = createRequire(import.meta.url); const contractPath = myRequire.resolve(`../src/orca.contract.js`); @@ -28,6 +33,43 @@ const scriptRoot = { // @ts-expect-error - XXX what's going on here?? const test = anyTest; +/** @type {IBCConnectionInfo} */ +const c1 = harden({ + id: 'connection-0', + client_id: 'client-0', + state: 3, // OPEN + counterparty: harden({ + client_id: 'client-0', + connection_id: 'connection-0', + prefix: { + key_prefix: 'key-prefix-0', + }, + }), + transferChannel: harden({ + portId: 'transfer', + channelId: 'channel-0', + counterPartyPortId: 'transfer', + counterPartyChannelId: 'channel-1', + ordering: 2, // ORDERED + version: '1', + state: 3, // OPEN + }), +}); + +/** @type {Record} */ +const chainInfo = harden({ + agoric: { + chainId: `agoriclocal`, + stakingTokens: [{ denom: 'ubld' }], + connections: { osmosislocal: c1 }, + }, + osmosis: { + chainId: `osmosislocal`, + stakingTokens: [{ denom: 'uosmo' }], + connections: { agoriclocal: c1 }, + }, +}); + /** * Tests assume access to the zoe service and that contracts are bundled. * @@ -37,237 +79,134 @@ const test = anyTest; * @param {import('ava').ExecutionContext} t */ const makeTestContext = async t => { - const { zoeService: zoe, feeMintAccess } = makeZoeKitForTest(); + const { powers } = await mockBootstrapPowers(t.log); + const bundleCache = await makeNodeBundleCache('bundles/', {}, s => import(s)); const bundle = await bundleCache.load(contractPath, 'orca'); const tools = await makeMockTools(t, bundleCache); - const makeDummyStorageNode = (nodeName = 'rootNode') => { - return Far('DummyStorageNode', { - makeChildNode: async childName => { - console.log(`makeChildNode called with name: ${childName}`); - return makeDummyStorageNode(childName); - }, - getPath: () => { - console.log(`getPath called on node: ${nodeName}`); - return `/${nodeName}`; - }, - toCapData: () => { - console.log(`toCapData called on node: ${nodeName}`); - return {}; - }, - setValue: value => { - console.log( - `setValue called on node: ${nodeName} with value: ${value}`, - ); - return value; - }, - }); - }; + const { agoricNamesAdmin } = await powers.consume; - const makeDummyMarshaller = () => { - return Far('DummyMarshaller', { - toCapData: data => { - console.log('toCapData called with data:', data); - return {}; - }, - fromCapData: capData => { - console.log('fromCapData called with capData:', capData); - return {}; - }, - }); - }; + for (const [name, info] of Object.entries(chainInfo)) { + await registerChain(agoricNamesAdmin, name, info); + } - const agoricNames = Far('DummyAgoricNames', { - lookup: async (key, name) => { - t.log('inside fake agoriname lookup'); - console.log('inside fake 2', key, name); - if (key === 'chain' && (name === 'agoric' || name === 'osmosis')) { - const state = harden({ - name, - chainId: `${name}local`, - denom: name === 'agoric' ? 'ubld' : 'uosmo', - expectedAddressPrefix: name === 'agoric' ? 'agoric' : 'osmo', - details: `${name} chain details`, - stakingTokens: [{ denom: name === 'agoric' ? 'ubld' : 'uosmo' }], - }); - - return harden({ - ...state, - makeAccount: Far('Account', { - getChainId: () => state.chainId, - getAccountAddress: () => `${state.name}AccountAddress`, - getBalance: () => `1000${state.denom}`, - }), - getChainInfo: Far('ChainInfo', { - getChainId: () => state.chainId, - getDenom: () => state.denom, - getExpectedAddressPrefix: () => state.expectedAddressPrefix, - }), - }); - } else if ( - key === 'chainConnection' && - (name === 'agoriclocal_osmosislocal' || - name === 'osmosislocal_agoriclocal') - ) { - return harden({ - connectionName: name, - sourceChain: name.split('_')[0], - destinationChain: name.split('_')[1], - transferChannel: harden({ - version: '1', - state: 'open', - portId: 'transfer', - ordering: 'ordered', - counterPartyPortId: 'transfer', - counterPartyChannelId: 'channel-1', - channelId: 'channel-0', - }), - state: 'active', - id: 'connection-0', - counterparty: harden({ - client_id: 'client-0', - connection_id: 'connection-0', - prefix: { - key_prefix: 'key-prefix-0', - }, + const zones = { + cosmos: powers.zone.subZone('cosmosInterchainService'), + }; + const cVowTools = prepareVowTools(zones.cosmos); + + /** @type {ReturnType} */ + const cosmosInterchainService = Far('CosmosInterchainService mock', { + makeAccount: (chainId, _host, _controller, _opts) => { + const denom = chainId.startsWith('agoric') ? 'ubld' : 'uosmo'; + + /** @type {IcaAccount} */ + const account = Far('Account', { + getChainId: () => chainId, + getAccountAddress: () => `${chainId}AccountAddress`, + getAddress: () => + harden({ + chainId, + value: `${chainId}AccountAddress`, + encoding: 'bech32', }), - client_id: 'client-0', - connectionDetails: `${name} connection details`, - }); - } - throw Error(`Chain or connection not found: ${name}`); + getBalance: () => `1000${denom}`, + deactivate: () => assert.fail(`mock 1`), + executeEncodedTx: () => assert.fail(`mock 2`), + executeTx: () => assert.fail(`mock 3`), + getLocalAddress: () => '/ibc-port/xyz', + getPort: () => assert.fail(`mock 4`), + getRemoteAddress: () => '/x/ibc-port/y/ordered/z', + reactivate: () => assert.fail(`mock 6`), + }); + + return cVowTools.asVow(() => account); + }, + provideICQConnection(_controllerConnectionId, _version) { + throw Error('mock'); }, }); - const cosmosInterchainService = Far('DummyCosmosInterchainService', { - getChainHub: async () => { - const chainHub = { - registerChain: async (name, details) => { - console.log(`chain registered: ${name}`, details); + /** @type {LocalChain} */ + const localchain = Far('Localchain mock', { + async makeAccount() { + /** @type {LocalChainAccount} */ + const acct = Far('LocalChainAccount mock', { + getAddress() { + return 'agoric123'; }, - getChain: async name => { - if ( - name === 'agoric' || - name === 'osmosis' || - name === 'agoriclocal' || - name === 'osmosislocal' - ) { - const state = harden({ - name, - chainId: `${name}local`, - denom: name === 'agoric' ? 'ubld' : 'uosmo', - expectedAddressPrefix: name === 'agoric' ? 'agoric' : 'osmo', - details: `${name} chain details`, - stakingTokens: [{ denom: name === 'agoric' ? 'ubld' : 'uosmo' }], - }); - - return harden({ - ...state, - makeAccount: () => - Far('Account', { - getChainId: () => state.chainId, - getAccountAddress: () => `${state.name}AccountAddress`, - getAddress: () => - harden({ - chainId: state.chainId, - value: `${state.name}AccountAddress`, - encoding: 'bech32', // or 'ethereum', based on your requirements - }), - getBalance: () => `1000${state.denom}`, - }), - getChainInfo: () => - Far('ChainInfo', { - getChainId: () => state.chainId, - getDenom: () => state.denom, - getExpectedAddressPrefix: () => state.expectedAddressPrefix, - }), - }); - } - throw Error(`chain not found: ${name}`); + getBalance() { + throw Error('mock TODO getBalance'); }, - }; - return chainHub; + deposit() { + throw Error('mock TODO makeAccount'); + }, + withdraw() { + throw Error('mock TODO withdraw'); + }, + executeTx() { + throw Error('mock TODO makeexecuteTxAccount'); + }, + async monitorTransfers(_tap) { + /** @type {TargetRegistration} */ + const reg = Far('TR mock', { + revoke() { + throw Error('TODO revoke'); + }, + updateTargetApp() { + throw Error('TODO updateTargetApp'); + }, + }); + return reg; + }, + }); + return acct; }, - makeAccount: async name => { - const chainHub = await E(cosmosInterchainService).getChainHub(); - const chain = await E(chainHub).getChain(name); - return E(chain).makeAccount(); + query() { + throw Error('mock TODO query'); }, - getChainInfo: async name => { - const chainHub = await E(cosmosInterchainService).getChainHub(); - const chain = await E(chainHub).getChain(name); - return E(chain).getChainInfo(); + queryMany() { + throw Error('mock TODO queryMany'); }, }); + const vowTools = prepareVowTools(powers.zone); + return { - zoe, bundle, bundleCache, - feeMintAccess, cosmosInterchainService, - agoricNames, - storageNode: makeDummyStorageNode(), - marshaller: makeDummyMarshaller(), + localchain, + vowTools, + bootstrapSpace: powers.consume, ...tools, }; }; -const makeQueryToolMock = () => { - return { - queryData: async path => { - console.log(`Querying data at path: ${path}`); - if (path.includes('wallet')) { - return { - offerToPublicSubscriberPaths: [['offerId', { account: 'osmo1abc' }]], - status: { - id: 'offerId', - numWantsSatisfied: 1, - result: 'UNPUBLISHED', - error: undefined, - }, - }; - } - return {}; - }, - }; -}; - test.before(async t => (t.context = await makeTestContext(t))); test('Install the contract', async t => { - const { zoe, bundle } = t.context; + const { bootstrapSpace, bundle } = t.context; + const { zoe } = bootstrapSpace; const installation = await E(zoe).install(bundle); t.log('installed:', installation); t.is(typeof installation, 'object'); }); test('Start Orca contract', async t => { - const { - zoe, - bundle, - cosmosInterchainService, - agoricNames, - storageNode, - marshaller, - } = t.context; - const installation = E(zoe).install(bundle); + const { bundle, bootstrapSpace, cosmosInterchainService, localchain } = + t.context; + const { zoe } = bootstrapSpace; + const installation = await E(zoe).install(bundle); const privateArgs = harden({ - // orchestration: Far('DummyOrchestration'), - // cosmosInterchainService: Far('DummyOrchestration'), - cosmosInterchainService, orchestrationService: cosmosInterchainService, - // storageNode: Far('DummyStorageNode'), - storageNode, - // marshaller: Far('DummyMarshaller'), - marshaller, - timer: Far('DummyTimer'), - timerService: Far('DummyTimer'), - localchain: Far('Dumm'), - // agoricNames: Far('agoricNames') - agoricNames, + storageNode: await bootstrapSpace.chainStorage, + marshaller: await E(bootstrapSpace.board).getPublishingMarshaller(), + timerService: await bootstrapSpace.chainTimerService, + localchain, + agoricNames: await bootstrapSpace.agoricNames, }); const { instance } = await E(zoe).startInstance( @@ -337,66 +276,30 @@ export const chainConfigs = { }, }; -test('Verify chain registration', async t => { - const { cosmosInterchainService } = t.context; - const chainHub = await E(cosmosInterchainService).getChainHub(); - - const agoricChain = await E(chainHub).getChain('agoric'); - t.truthy(agoricChain, 'Agoric chain should be registered'); - - const osmosisChain = await E(chainHub).getChain('osmosis'); - t.truthy(osmosisChain, 'Osmosis chain should be registered'); -}); - -const queryVstorage = async (t, qt, wallet, offerId) => { - t.log(`querying vstorage for wallet: ${wallet}, offerId: ${offerId}`); - const currentWalletRecord = await qt.queryData( - `published.wallet.${wallet}.current`, - ); - t.log('current wallet record:', currentWalletRecord); - const offerToPublicSubscriberMap = Object.fromEntries( - currentWalletRecord.offerToPublicSubscriberPaths, - ); - t.log('offer to public subscriber map:', offerToPublicSubscriberMap); - const address = offerToPublicSubscriberMap[offerId]?.account.split('.').pop(); - t.log('extracted address:', address); - return { address, currentWalletRecord }; -}; - -const logVstorageState = async (t, qt, path) => { - const data = await qt.queryData(path); - t.log(`vstorage data at ${path}:`, data); -}; - const orchestrationAccountScenario = test.macro({ title: (_, chainName) => `orchestrate - ${chainName} makeAccount returns a ContinuingOfferResult`, exec: async (t, chainName) => { - const config = chainConfigs[chainName]; - if (!config) { - return t.fail(`unknown chain: ${chainName}`); - } - const { - zoe, bundle, cosmosInterchainService, - agoricNames, - storageNode, - marshaller, + localchain, + bootstrapSpace, + vowTools: vt, } = t.context; - t.log('installing the contract...'); - const installation = E(zoe).install(bundle); + const { zoe } = bootstrapSpace; + + t.log('installing the contract...'); // why are we doing this again??? + /** @type {Installation} */ + const installation = await E(zoe).install(bundle); const privateArgs = harden({ - cosmosInterchainService, orchestrationService: cosmosInterchainService, - storageNode, - marshaller, - timer: Far('DummyTimer'), - timerService: Far('DummyTimer'), - localchain: Far('DummyLocalchain'), - agoricNames, + storageNode: await bootstrapSpace.chainStorage, + marshaller: await E(bootstrapSpace.board).getPublishingMarshaller(), + timerService: await bootstrapSpace.chainTimerService, + localchain, + agoricNames: await bootstrapSpace.agoricNames, }); t.log('starting the instance...'); @@ -406,77 +309,31 @@ const orchestrationAccountScenario = test.macro({ {}, privateArgs, ); - t.log('instance started:', instance); - t.truthy(instance); - - t.log('getting public facet...'); const publicFacet = await E(zoe).getPublicFacet(instance); - t.log('public facet obtained:', publicFacet); t.log('creating account invitation...'); - const initialInvitation = await E(publicFacet).makeAccountInvitation(); - t.log('invitation created:', initialInvitation); - - const makeAccountOffer = { - give: {}, - want: {}, - exit: { onDemand: null }, - }; + const toMakeAccount = await E(publicFacet).makeAccountInvitation(); - t.log('making offer...'); - const offerId = 'offerId'; - const initialUserSeat = await E(zoe).offer( - initialInvitation, - makeAccountOffer, - undefined, - { id: offerId }, - ); - t.log('initial user seat:', initialUserSeat); + t.log('making offer...', chainName); + const seat = await E(zoe).offer(toMakeAccount, {}, undefined, { + chainName, + }); - t.log('getting offer result...'); - const offerResult = await E(initialUserSeat).getOfferResult(); + const offerResult = await vt.when(E(seat).getOfferResult()); t.log('offer result:', offerResult); - t.truthy(offerResult, 'Offer result should exist'); - - const qt = makeQueryToolMock(); - const wallet = 'test-wallet'; - // log vstorage state before querying - await logVstorageState(t, qt, 'published.agoricNames'); - - const { address, currentWalletRecord } = await queryVstorage( - t, - qt, - wallet, - offerId, - ); - - t.log('got address:', address); - t.regex( - address, - new RegExp(`^${config.expectedAddressPrefix}1`), - `Address for ${chainName} is valid`, + t.is( + passStyleOf(offerResult.invitationMakers), + 'remotable', + 'Offer include invitationMakers', ); - t.log('current wallet record', currentWalletRecord); - - const continuingInvitation = await E(publicFacet).makeAccountInvitation(); - t.truthy(continuingInvitation, 'continuing invitation should be created'); - - const continuingOffer = { - give: {}, - want: {}, - exit: { onDemand: null }, - }; - - const continuingUserSeat = await E(zoe).offer( - continuingInvitation, - continuingOffer, - undefined, - { previousOffer: offerId }, - ); - const continuingOfferResult = await E(continuingUserSeat).getOfferResult(); - - t.truthy(continuingOfferResult, 'continuing offer should produce a result'); - t.log('continuing offer result', continuingOfferResult); + t.like(offerResult, { + publicSubscribers: { + account: { + description: 'Staking Account holder status', + storagePath: 'mockChainStorageRoot.osmosislocalAccountAddress', + }, + }, + }); }, }); @@ -484,108 +341,67 @@ const orchestrationAccountAndFundScenario = test.macro({ title: (_, chainName) => `orchestrate - ${chainName} makeAccount and fund returns a ContinuingOfferResult`, exec: async (t, chainName) => { - const config = chainConfigs[chainName]; - if (!config) { - return t.fail(`unknown chain: ${chainName}`); - } - const { - zoe, bundle, + bootstrapSpace, cosmosInterchainService, - agoricNames, - storageNode, - marshaller, + localchain, + vowTools: vt, } = t.context; + const { zoe } = bootstrapSpace; t.log('installing the contract...'); - const installation = E(zoe).install(bundle); + const installation = await E(zoe).install(bundle); const privateArgs = harden({ - cosmosInterchainService, orchestrationService: cosmosInterchainService, - storageNode, - marshaller, - timer: Far('DummyTimer'), - timerService: Far('DummyTimer'), - localchain: Far('DummyLocalchain'), - agoricNames, + storageNode: await bootstrapSpace.chainStorage, + marshaller: await E(bootstrapSpace.board).getPublishingMarshaller(), + timerService: await bootstrapSpace.chainTimerService, + localchain, + agoricNames: await bootstrapSpace.agoricNames, }); - const { mint, issuer, brand } = makeIssuerKit('BLD'); - - const issuers = { - BLDIssuer: issuer, - }; + const BLD = await bootstrapSpace.bldIssuerKit; t.log('starting the instance...'); const { instance } = await E(zoe).startInstance( installation, - issuers, + { BLD: BLD.issuer }, {}, privateArgs, ); - t.log('instance started:', instance); - t.truthy(instance); - - t.log('getting public facet...'); const publicFacet = await E(zoe).getPublicFacet(instance); - t.log('public facet obtained:', publicFacet); t.log('creating account invitation...'); - const initialInvitation = - await E(publicFacet).makeCreateAndFundInvitation(); - t.log('invitation created:', initialInvitation); - - t.log('brand', brand); - const amount = AmountMath.make(brand, 1n); - const makeAccountOffer = { - give: { Deposit: amount }, - want: {}, - exit: { onDemand: null }, - }; - - const bldPurse = issuer.makeEmptyPurse(); - const payment = mint.mintPayment(amount); - bldPurse.deposit(payment); - const withdrawnDeposit = await E(bldPurse).withdraw(amount); - t.log('withdrawnDeposit', withdrawnDeposit); + const toCreateAndFund = await E(publicFacet).makeCreateAndFundInvitation(); + + const Deposit = AmountMath.make(BLD.brand, 1n); + const payment = BLD.mint.mintPayment(Deposit); t.log('making offer...'); - const offerId = 'offerId'; - const initialUserSeat = await E(zoe).offer( - initialInvitation, - makeAccountOffer, - { - Deposit: withdrawnDeposit, - }, - { - id: offerId, - }, + const seat = await E(zoe).offer( + toCreateAndFund, + { give: { Deposit } }, + { Deposit: payment }, + { chainName }, ); - t.log('initial user seat:', initialUserSeat); - t.log('getting offer result...'); - const offerResult = await E(initialUserSeat).getOfferResult(); + const offerResult = await vt.when(E(seat).getOfferResult()); t.log('offer result:', offerResult); t.truthy(offerResult, 'Offer result should exist'); - const qt = makeQueryToolMock(); - const wallet = 'test-wallet'; - await logVstorageState(t, qt, 'published.agoricNames'); - - const { address, currentWalletRecord } = await queryVstorage( - t, - qt, - wallet, - offerId, + t.is( + passStyleOf(offerResult.invitationMakers), + 'remotable', + 'Offer include invitationMakers', ); - - t.log('got address:', address); - t.regex( - address, - new RegExp(`^${config.expectedAddressPrefix}1`), - `Address for ${chainName} is valid`, - ); - t.log('current wallet record', currentWalletRecord); + t.like(offerResult, { + publicSubscribers: { + account: { + description: 'Staking Account holder status', + storagePath: 'mockChainStorageRoot.osmosislocalAccountAddress', + }, + }, + }); }, });