diff --git a/packages/boot/test/orchestration/restart-contracts.test.ts b/packages/boot/test/orchestration/restart-contracts.test.ts new file mode 100644 index 000000000000..96013a65b59f --- /dev/null +++ b/packages/boot/test/orchestration/restart-contracts.test.ts @@ -0,0 +1,169 @@ +/** @file Bootstrap test of restarting contracts using orchestration */ +import { test as anyTest } from '@agoric/zoe/tools/prepare-test-env-ava.js'; +import { TestFn } from 'ava'; + +import type { CosmosValidatorAddress } from '@agoric/orchestration'; +import type { UpdateRecord } from '@agoric/smart-wallet/src/smartWallet.js'; +import { + makeWalletFactoryContext, + type WalletFactoryTestContext, +} from '../bootstrapTests/walletFactory.ts'; + +const test: TestFn = anyTest; +test.before(async t => { + t.context = await makeWalletFactoryContext( + t, + '@agoric/vm-config/decentral-itest-orchestration-config.json', + ); +}); +test.after.always(t => t.context.shutdown?.()); + +// Not interesting because it doesn't wait on other chains. Leaving here because maybe it will before it's done. +test.serial('sendAnywhere', async t => { + const { + walletFactoryDriver, + buildProposal, + evalProposal, + flushInboundQueue, + } = t.context; + + const { IST } = t.context.agoricNamesRemotes.brand; + + t.log('start sendAnywhere'); + await evalProposal( + buildProposal('@agoric/builders/scripts/testing/start-sendAnywhere.js'), + ); + + t.log('making offer'); + const wallet = await walletFactoryDriver.provideSmartWallet('agoric1test'); + // no money in wallet to actually send + const zero = { brand: IST, value: 0n }; + // send because it won't resolve + await wallet.sendOffer({ + id: 'send-somewhere', + invitationSpec: { + source: 'agoricContract', + instancePath: ['sendAnywhere'], + callPipe: [['makeSendInvitation']], + }, + proposal: { + // @ts-expect-error XXX BoardRemote + give: { Send: zero }, + }, + offerArgs: { + // meaningless address + destAddr: 'cosmos1qy352eufjjmc9c', + chainName: 'cosmoshub', + }, + }); + // no errors and no resolution + const beforeFlush = wallet.getLatestUpdateRecord(); + t.like(wallet.getLatestUpdateRecord(), { + updated: 'offerStatus', + status: { + id: 'send-somewhere', + error: undefined, + }, + numWantsSatisfied: undefined, + payouts: undefined, + result: undefined, + }); + + t.is(await flushInboundQueue(), 0); + t.deepEqual(wallet.getLatestUpdateRecord(), beforeFlush); + + t.log('restart sendAnywhere'); + await evalProposal( + buildProposal('@agoric/builders/scripts/testing/restart-sendAnywhere.js'), + ); + + const conclusion = wallet.getLatestUpdateRecord(); + console.log('conclusion', conclusion); + t.like(conclusion, { + updated: 'offerStatus', + status: { + id: 'send-somewhere', + error: undefined, + }, + numWantsSatisfied: undefined, + payouts: undefined, + result: undefined, + }); + + await flushInboundQueue(); + + // Nothing interesting to confirm here. +}); + +const validatorAddress: CosmosValidatorAddress = { + value: 'cosmosvaloper1test', + chainId: 'gaiatest', + encoding: 'bech32', +}; + +// check for key because the value will be 'undefined' when the result is provided +// TODO should it be something truthy? +const hasResult = (r: UpdateRecord) => { + assert(r.updated === 'offerStatus'); + return 'result' in r.status; +}; + +// Tests restart but not of an orchestration() flow +test('stakeAtom', async t => { + const { + buildProposal, + evalProposal, + agoricNamesRemotes, + flushInboundQueue, + readLatest, + } = t.context; + + await evalProposal( + buildProposal('@agoric/builders/scripts/orchestration/init-stakeAtom.js'), + ); + + const wd = await t.context.walletFactoryDriver.provideSmartWallet( + 'agoric1testStakAtom', + ); + + await wd.sendOffer({ + id: 'request-account', + invitationSpec: { + source: 'agoricContract', + instancePath: ['stakeAtom'], + callPipe: [['makeAccountInvitationMaker']], + }, + proposal: {}, + }); + // cosmos1test is from ibc/mocks.js + const accountPath = 'published.stakeAtom.accounts.cosmos1test'; + t.throws(() => readLatest(accountPath)); + t.is(await flushInboundQueue(), 1); + t.is(readLatest(accountPath), ''); + // request-account is complete + + const { ATOM } = agoricNamesRemotes.brand; + assert(ATOM); + + await wd.sendOffer({ + id: 'request-delegate', + invitationSpec: { + source: 'continuing', + previousOffer: 'request-account', + invitationMakerName: 'Delegate', + invitationArgs: [validatorAddress, { brand: ATOM, value: 10n }], + }, + proposal: {}, + }); + // no result yet because the IBC incoming messages haven't arrived + // and won't until we flush. + t.false(hasResult(wd.getLatestUpdateRecord())); + + t.log('restart stakeAtom'); + await evalProposal( + buildProposal('@agoric/builders/scripts/testing/restart-stakeAtom.js'), + ); + + t.is(await flushInboundQueue(), 1); + t.true(hasResult(wd.getLatestUpdateRecord())); +}); diff --git a/packages/builders/scripts/testing/restart-sendAnywhere.js b/packages/builders/scripts/testing/restart-sendAnywhere.js new file mode 100644 index 000000000000..322e46c3257a --- /dev/null +++ b/packages/builders/scripts/testing/restart-sendAnywhere.js @@ -0,0 +1,99 @@ +/** + * @file This is for use in tests in a3p-integration + * Unlike most builder scripts, this one includes the proposal exports as well. + */ +import { + deeplyFulfilledObject, + makeTracer, + NonNullish, +} from '@agoric/internal'; +import { E } from '@endo/far'; + +/// + +const trace = makeTracer('StartSA', true); + +/** + * @import {start as StartFn} from '@agoric/orchestration/src/examples/sendAnywhere.contract.js'; + */ + +/** + * @param {BootstrapPowers} powers + */ +export const restartSendAnywhere = async ({ + consume: { + agoricNames, + board, + chainStorage, + chainTimerService, + cosmosInterchainService, + localchain, + + contractKits, + }, + instance: instances, +}) => { + trace(restartSendAnywhere.name); + + // @ts-expect-error unknown instance + const instance = await instances.consume.sendAnywhere; + trace('instance', instance); + const kit = await E(contractKits).get(instance); + + const marshaller = await E(board).getReadonlyMarshaller(); + + const privateArgs = await deeplyFulfilledObject( + harden({ + agoricNames, + localchain, + marshaller, + orchestrationService: cosmosInterchainService, + storageNode: E(NonNullish(await chainStorage)).makeChildNode( + 'sendAnywhere', + ), + timerService: chainTimerService, + }), + ); + + await E(kit.adminFacet).restartContract(privateArgs); + trace('done'); +}; +harden(restartSendAnywhere); + +export const getManifest = () => { + return { + manifest: { + [restartSendAnywhere.name]: { + consume: { + agoricNames: true, + board: true, + chainStorage: true, + chainTimerService: true, + cosmosInterchainService: true, + localchain: true, + + contractKits: true, + }, + instance: { + consume: { sendAnywhere: true }, + }, + }, + }, + }; +}; + +/** @type {import('@agoric/deploy-script-support/src/externalTypes.js').CoreEvalBuilder} */ +export const defaultProposalBuilder = async () => + harden({ + // Somewhat unorthodox, source the exports from this builder module + sourceSpec: '@agoric/builders/scripts/testing/restart-sendAnywhere.js', + getManifestCall: [getManifest.name], + }); + +export default async (homeP, endowments) => { + // import dynamically so the module can work in CoreEval environment + const dspModule = await import('@agoric/deploy-script-support'); + const { makeHelpers } = dspModule; + const { writeCoreEval } = await makeHelpers(homeP, endowments); + await writeCoreEval(restartSendAnywhere.name, defaultProposalBuilder); +}; diff --git a/packages/builders/scripts/testing/restart-stakeAtom.js b/packages/builders/scripts/testing/restart-stakeAtom.js new file mode 100644 index 000000000000..9250b3399f64 --- /dev/null +++ b/packages/builders/scripts/testing/restart-stakeAtom.js @@ -0,0 +1,92 @@ +/** + * @file This is for use in tests in a3p-integration + * Unlike most builder scripts, this one includes the proposal exports as well. + */ +import { + deeplyFulfilledObject, + makeTracer, + NonNullish, +} from '@agoric/internal'; +import { makeStorageNodeChild } from '@agoric/internal/src/lib-chainStorage.js'; +import { E } from '@endo/far'; + +/// + +const trace = makeTracer('RestartSA', true); + +/** + * @import {start as StartFn} from '@agoric/orchestration/src/examples/stakeIca.contract.js'; + */ + +/** + * @param {BootstrapPowers} powers + */ +export const restartStakeAtom = async ({ + consume: { + board, + chainStorage, + chainTimerService, + cosmosInterchainService, + + contractKits, + }, + instance: instances, +}) => { + trace(restartStakeAtom.name); + + const instance = await instances.consume.stakeAtom; + trace('instance', instance); + /** @type {StartedInstanceKit} */ + const kit = await E(contractKits).get(instance); + + const marshaller = await E(board).getReadonlyMarshaller(); + + const privateArgs = await deeplyFulfilledObject( + harden({ + cosmosInterchainService: cosmosInterchainService, + storageNode: makeStorageNodeChild(chainStorage, 'stakeAtom'), + marshaller, + timer: chainTimerService, + }), + ); + + await E(kit.adminFacet).restartContract(privateArgs); + trace('done'); +}; +harden(restartStakeAtom); + +export const getManifest = () => { + return { + manifest: { + [restartStakeAtom.name]: { + consume: { + board: true, + chainStorage: true, + chainTimerService: true, + cosmosInterchainService: true, + + contractKits: true, + }, + instance: { + consume: { stakeAtom: true }, + }, + }, + }, + }; +}; + +/** @type {import('@agoric/deploy-script-support/src/externalTypes.js').CoreEvalBuilder} */ +export const defaultProposalBuilder = async () => + harden({ + // Somewhat unorthodox, source the exports from this builder module + sourceSpec: '@agoric/builders/scripts/testing/restart-stakeAtom.js', + getManifestCall: [getManifest.name], + }); + +export default async (homeP, endowments) => { + // import dynamically so the module can work in CoreEval environment + const dspModule = await import('@agoric/deploy-script-support'); + const { makeHelpers } = dspModule; + const { writeCoreEval } = await makeHelpers(homeP, endowments); + await writeCoreEval(restartStakeAtom.name, defaultProposalBuilder); +}; diff --git a/packages/builders/scripts/testing/start-sendAnywhere.js b/packages/builders/scripts/testing/start-sendAnywhere.js new file mode 100644 index 000000000000..87bc18cd2a73 --- /dev/null +++ b/packages/builders/scripts/testing/start-sendAnywhere.js @@ -0,0 +1,128 @@ +/** + * @file This is for use in tests in a3p-integration + * Unlike most builder scripts, this one includes the proposal exports as well. + */ +import { + deeplyFulfilledObject, + makeTracer, + NonNullish, +} from '@agoric/internal'; +import { E } from '@endo/far'; + +/// +/** + * @import {Installation} from '@agoric/zoe/src/zoeService/utils.js'; + */ + +const trace = makeTracer('StartSA', true); + +/** + * @import {start as StartFn} from '@agoric/orchestration/src/examples/sendAnywhere.contract.js'; + */ + +/** + * @param {BootstrapPowers & { + * installation: { + * consume: { + * sendAnywhere: Installation; + * }; + * }; + * }} powers + */ +export const startSendAnywhere = async ({ + consume: { + agoricNames, + board, + chainStorage, + chainTimerService, + cosmosInterchainService, + localchain, + startUpgradable, + }, + installation: { + consume: { sendAnywhere }, + }, + instance: { + // @ts-expect-error unknown instance + produce: { sendAnywhere: produceInstance }, + }, +}) => { + trace(startSendAnywhere.name); + + const marshaller = await E(board).getReadonlyMarshaller(); + + const privateArgs = await deeplyFulfilledObject( + harden({ + agoricNames, + localchain, + marshaller, + orchestrationService: cosmosInterchainService, + storageNode: E(NonNullish(await chainStorage)).makeChildNode( + 'sendAnywhere', + ), + timerService: chainTimerService, + }), + ); + + const { instance } = await E(startUpgradable)({ + label: 'sendAnywhere', + installation: sendAnywhere, + privateArgs, + }); + produceInstance.resolve(instance); + trace('done'); +}; +harden(startSendAnywhere); + +export const getManifest = ({ restoreRef }, { installationRef }) => { + return { + manifest: { + [startSendAnywhere.name]: { + consume: { + agoricNames: true, + board: true, + chainStorage: true, + chainTimerService: true, + cosmosInterchainService: true, + localchain: true, + + startUpgradable: true, + }, + installation: { + consume: { sendAnywhere: true }, + }, + instance: { + produce: { sendAnywhere: true }, + }, + }, + }, + installations: { + sendAnywhere: restoreRef(installationRef), + }, + }; +}; + +/** @type {import('@agoric/deploy-script-support/src/externalTypes.js').CoreEvalBuilder} */ +export const defaultProposalBuilder = async ({ publishRef, install }) => + harden({ + // Somewhat unorthodox, source the exports from this builder module + sourceSpec: '@agoric/builders/scripts/testing/start-sendAnywhere.js', + getManifestCall: [ + getManifest.name, + { + installationRef: publishRef( + install( + '@agoric/orchestration/src/examples/sendAnywhere.contract.js', + ), + ), + }, + ], + }); + +export default async (homeP, endowments) => { + // import dynamically so the module can work in CoreEval environment + const dspModule = await import('@agoric/deploy-script-support'); + const { makeHelpers } = dspModule; + const { writeCoreEval } = await makeHelpers(homeP, endowments); + await writeCoreEval(startSendAnywhere.name, defaultProposalBuilder); +};