From 2618ef4a3af05b053d3c67cb1f41a23463aca9a6 Mon Sep 17 00:00:00 2001 From: Siarhei Liakh Date: Wed, 8 Jan 2025 15:12:51 +0000 Subject: [PATCH 1/9] feat(cosmic-swingset): introduce GovernorExecutor #10725 refs: #10725 Initial set of changes to get auctioneer governors terminated. --- golang/cosmos/app/upgrade.go | 20 ++ .../scripts/vats/upgrade-governor-instance.js | 206 ++++++++++++++++++ .../src/contractGovernorExecutor.js | 26 +++ 3 files changed, 252 insertions(+) create mode 100644 packages/builders/scripts/vats/upgrade-governor-instance.js create mode 100644 packages/governance/src/contractGovernorExecutor.js diff --git a/golang/cosmos/app/upgrade.go b/golang/cosmos/app/upgrade.go index da3407ecd40..657d76ce85e 100644 --- a/golang/cosmos/app/upgrade.go +++ b/golang/cosmos/app/upgrade.go @@ -192,6 +192,26 @@ func terminateGovernorCoreProposal(upgradeName string) (vm.CoreProposalStep, err ) } +func upgradeGovernorExecutorCoreProposal(upgradeName string) (vm.CoreProposalStep, error) { + // targets is a slice of "$boardID:$instanceKitLabel" strings. + var targets []string + switch getVariantFromUpgradeName(upgradeName) { + case "EMERYNET": + targets = []string{"board04149:auctioneer"} // v38: governor for v39 + //"boardXXXYYY:autioneer", + // fixme: need some targets here + default: + return nil, nil + } + + return buildProposalStepWithArgs( + "@agoric/builders/scripts/vats/upgrade-governor-instance.js", + // Request `defaultProposalBuilder(powers, targets)`. + "defaultProposalBuilder", + []any{targets}, + ) +} + // func upgradeMintHolderCoreProposal(upgradeName string) (vm.CoreProposalStep, error) { // variant := getVariantFromUpgradeName(upgradeName) diff --git a/packages/builders/scripts/vats/upgrade-governor-instance.js b/packages/builders/scripts/vats/upgrade-governor-instance.js new file mode 100644 index 00000000000..88d1a0d4050 --- /dev/null +++ b/packages/builders/scripts/vats/upgrade-governor-instance.js @@ -0,0 +1,206 @@ +/** + * @file Upgrade price-feed governor instances such as emerynet v664. + * Functions as both an off-chain builder and an on-chain core-eval. + */ + +/// + +import { E } from '@endo/far'; + +const SELF = '@agoric/builders/scripts/vats/terminate-governor-instance.js'; +const USAGE = `Usage: agoric run /path/to/terminate-governor-instance.js \\ + <$governorInstanceHandleBoardID:$instanceKitLabel>...`; + +const repr = val => + typeof val === 'string' || (typeof val === 'object' && val !== null) + ? JSON.stringify(val) + : String(val); +const defaultMakeError = (strings, ...subs) => + Error( + strings.map((s, i) => `${i === 0 ? '' : repr(subs[i - 1])}${s}`).join(''), + ); +const makeUsageError = (strings, ...subs) => { + const err = defaultMakeError(strings, ...subs); + console.error(err.message); + console.error(USAGE); + return err; +}; + +const rtarget = /^(?board[0-9]+):(?.+)$/; +/** + * @param {string[]} args + * @param {(strings: TemplateStringsArray | string[], ...subs: unknown[]) => Error} [makeError] + * @returns {Array<{boardID: string, instanceKitLabel: string}>} + */ +const parseTargets = (args = [], makeError = defaultMakeError) => { + if (!Array.isArray(args)) throw makeError`invalid targets: ${args}`; + /** @type {Array<{boardID: string, instanceKitLabel: string}>} */ + const targets = []; + const badTargets = []; + for (const arg of args) { + const m = typeof arg === 'string' && arg.match(rtarget); + if (!m) { + badTargets.push(arg); + } else { + // @ts-expect-error cast + targets.push(m.groups); + } + } + if (badTargets.length !== 0) { + throw makeError`malformed target(s): ${badTargets}`; + } else if (targets.length === 0) { + throw makeError`no target(s)`; + } + return targets; +}; + +/** + * @param {BootstrapPowers} powers + * @param {{ options: { governorExecutorBundle: any, targetSpecifiers: string[] } }} config + */ +export const upgradeGovernors = async ( + { consume: { board, governedContractKits } }, + { options: { governorExecutorBundle, targetSpecifiers } }, +) => { + const { Fail, quote: q } = assert; + const targets = parseTargets(targetSpecifiers, Fail); + const doneP = Promise.allSettled( + targets.map(async ({ boardID, instanceKitLabel }) => { + const logLabel = [boardID, instanceKitLabel]; + const contractInstanceHandle = await E(board).getValue(boardID); + const instanceKit = await E(governedContractKits).get( + // @ts-expect-error TS2345 Property '[tag]' is missing + contractInstanceHandle, + ); + console.log( + `${q(logLabel)} alleged governor contract instance kit`, + instanceKit, + ); + const { label, governorAdminFacet, adminFacet } = instanceKit; + label === instanceKitLabel || + Fail`${q(logLabel)} unexpected instanceKit label, got ${label} but wanted ${q(instanceKitLabel)}`; + (adminFacet && adminFacet !== governorAdminFacet) || + Fail`${q(logLabel)} instanceKit adminFacet should have been present and different from governorAdminFacet but was ${adminFacet}`; + const reason = harden(Error(`core-eval terminating ${label} governor`)); + await E(governorAdminFacet).upgradeContract(governorExecutorBundle, undefined); + console.log(`${q(logLabel)} terminated governor`); + }), + ); + const results = await doneP; + const problems = targets.flatMap(({ boardID, instanceKitLabel }, i) => { + if (results[i].status === 'fulfilled') return []; + return [[boardID, instanceKitLabel, results[i].reason]]; + }); + if (problems.length !== 0) { + console.error('governor termination(s) failed', problems); + Fail`governor termination(s) failed: ${problems}`; + } +}; +harden(upgradeGovernors); + +/* +export const getManifest = (_powers, targetSpecifiers) => { + parseTargets(targetSpecifiers); + return { + manifest: { + [upgradeGovernors.name]: { + consume: { board: true, governedContractKits: true }, + }, + }, + // Provide `terminateGovernors` a second argument like + // `{ options: { targetSpecifiers } }`. + options: { targetSpecifiers }, + }; +}; +*/ + +const uG = 'upgradeGovernors'; +/** + * Return the manifest, installations, and options for upgrading Vaults. + * + * @param {object} utils + * @param {any} utils.restoreRef + * @param {any} vaultUpgradeOptions + */ +export const getManifest = async ( + { restoreRef }, + { bundleRef, targetSpecifiers }, +) => { + return { + manifest: { + [upgradeGovernors.name]: { + consume: { + board: uG, + zoe: uG, + governedContractKits: uG, + }, + }, + }, + installations: { governorExecutor: restoreRef(bundleRef) }, // do we need installations ??? + options: { governorExecutorBundle: bundleRef, targetSpecifiers }, + }; +}; + +/* @type {import('@agoric/deploy-script-support/src/externalTypes.js').CoreEvalBuilder} */ +/* +export const defaultProposalBuilder = async (_utils, targetSpecifiers) => { + parseTargets(targetSpecifiers); + return harden({ + sourceSpec: SELF, + getManifestCall: ['getManifest', targetSpecifiers], + }); +};*/ + +/* @type {import('@agoric/deploy-script-support/src/externalTypes.js').DeployScriptFunction} */ +/* +export default async (homeP, endowments) => { + const { scriptArgs } = endowments; + parseTargets(scriptArgs, makeUsageError); + const dspModule = await import('@agoric/deploy-script-support'); + const { makeHelpers } = dspModule; + const { writeCoreEval } = await makeHelpers(homeP, endowments); + await writeCoreEval(upgradeGovernors.name, utils => + defaultProposalBuilder(utils, scriptArgs), + ); +}; +*/ +// import { makeHelpers } from '@agoric/deploy-script-support'; + +/** @type {import('@agoric/deploy-script-support/src/externalTypes.js').CoreEvalBuilder} */ +export const defaultProposalBuilder = async ({ publishRef, install }, targetSpecifiers) => { + parseTargets(targetSpecifiers); + return harden({ + sourceSpec: SELF, + getManifestCall: [ + getManifest.name, + { + bundleRef: publishRef( + install( + '@agoric/governance/bundles/contractGovernorExecutor.js', + ), + ), + targetSpecifiers, + }, + ], + }); +} + +/* @type {import('@agoric/deploy-script-support/src/externalTypes.js').DeployScriptFunction} */ +/* +export default async (homeP, endowments) => { + const { writeCoreEval } = await makeHelpers(homeP, endowments); + await writeCoreEval('upgrade-governor-instance', defaultProposalBuilder); +}; +*/ + +/** @type {import('@agoric/deploy-script-support/src/externalTypes.js').DeployScriptFunction} */ +export default async (homeP, endowments) => { + const { scriptArgs } = endowments; + parseTargets(scriptArgs, makeUsageError); + const dspModule = await import('@agoric/deploy-script-support'); + const { makeHelpers } = dspModule; + const { writeCoreEval } = await makeHelpers(homeP, endowments); + await writeCoreEval(upgradeGovernors.name, utils => + defaultProposalBuilder(utils, scriptArgs), + ); +}; \ No newline at end of file diff --git a/packages/governance/src/contractGovernorExecutor.js b/packages/governance/src/contractGovernorExecutor.js new file mode 100644 index 00000000000..5ca9c73617a --- /dev/null +++ b/packages/governance/src/contractGovernorExecutor.js @@ -0,0 +1,26 @@ +import { E } from '@endo/eventual-send'; +import { makeTracer } from '@agoric/internal'; + +const trace = makeTracer('CGExec', false); + +/** @type {ContractMeta} */ +export const meta = { + upgradability: 'canUpgrade', +}; +harden(meta); + +/** + * Start an instance of a governor, governing a "governed" contract specified in terms. + * + * @param {ZCF<{}>} zcf + * @param {{}} _privateArgs + * @param {import('@agoric/vat-data').Baggage} baggage + */ +export const start = async (zcf, _privateArgs, baggage) => { + trace('start'); + const contractInstanceAdminFacet = baggage.get('creatorFacet'); + const terminationData = harden(Error(`termination of contract by executor governor`)); + await E(contractInstanceAdminFacet).terminateContract(terminationData); + zcf.shutdown(`self-termination of executor governor`); +}; +harden(start); From 3215d635e024e6fbe5e73a022608f49244cea24b Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Thu, 9 Jan 2025 11:46:17 -0600 Subject: [PATCH 2/9] test: find contract via governor; kill them both - contractGovernorExecutor: define ContractGovernorKit kind - upgrade-governor-instance: - fix SELF - some docs - rename to governorExecutorBundleId - get .bundleID out of bundleRef - move governor termination from self.shutdown to core eval --- .../bootstrapTests/execute-governed.test.ts | 32 ++++++++++++++ .../scripts/vats/upgrade-governor-instance.js | 37 ++++++++++------ .../src/contractGovernorExecutor.js | 42 +++++++++++++++++-- 3 files changed, 94 insertions(+), 17 deletions(-) create mode 100644 packages/boot/test/bootstrapTests/execute-governed.test.ts diff --git a/packages/boot/test/bootstrapTests/execute-governed.test.ts b/packages/boot/test/bootstrapTests/execute-governed.test.ts new file mode 100644 index 00000000000..2ed0f6a7617 --- /dev/null +++ b/packages/boot/test/bootstrapTests/execute-governed.test.ts @@ -0,0 +1,32 @@ +import { test as anyTest } from '@agoric/zoe/tools/prepare-test-env-ava.js'; +import type { TestFn } from 'ava'; +import { + makeWalletFactoryContext, + type WalletFactoryTestContext, +} from './walletFactory.js'; + +// swingSetTestKit is the part we're really interested in +const test: TestFn = anyTest; + +test.before('bootstrap', async t => { + const config = '@agoric/vm-config/decentral-itest-orchestration-config.json'; + t.context = await makeWalletFactoryContext(t, config, {}); +}); +test.after.always(t => t.context.shutdown?.()); + +test.serial('terminate a bad auctioneer & its governor', async t => { + const { buildProposal, evalProposal } = t.context; + + const targets = [ + 'board03040:ATOM-USD_price_feed', // TODO: auctioneer + ]; + + const materials = buildProposal( + '@agoric/builders/scripts/vats/upgrade-governor-instance.js', + targets, + ); + await evalProposal(materials); + + t.log('TODO: check that the price feed is really dead'); + t.pass(); +}); diff --git a/packages/builders/scripts/vats/upgrade-governor-instance.js b/packages/builders/scripts/vats/upgrade-governor-instance.js index 88d1a0d4050..af4b8526100 100644 --- a/packages/builders/scripts/vats/upgrade-governor-instance.js +++ b/packages/builders/scripts/vats/upgrade-governor-instance.js @@ -7,8 +7,8 @@ import { E } from '@endo/far'; -const SELF = '@agoric/builders/scripts/vats/terminate-governor-instance.js'; -const USAGE = `Usage: agoric run /path/to/terminate-governor-instance.js \\ +const SELF = '@agoric/builders/scripts/vats/upgrade-governor-instance.js'; +const USAGE = `Usage: agoric run /path/to/upgrade-governor-instance.js \\ <$governorInstanceHandleBoardID:$instanceKitLabel>...`; const repr = val => @@ -55,14 +55,19 @@ const parseTargets = (args = [], makeError = defaultMakeError) => { }; /** + * Given boardIDs of some instances of contract governors, + * upgrade those governors to contractGovernorExecutor.js, + * which will terminate the governed contracts. + * * @param {BootstrapPowers} powers - * @param {{ options: { governorExecutorBundle: any, targetSpecifiers: string[] } }} config + * @param {{ options: { governorExecutorBundleId: string, targetSpecifiers: string[] } }} config */ export const upgradeGovernors = async ( { consume: { board, governedContractKits } }, - { options: { governorExecutorBundle, targetSpecifiers } }, + { options: { governorExecutorBundleId, targetSpecifiers } }, ) => { const { Fail, quote: q } = assert; + assert.typeof(governorExecutorBundleId, 'string'); const targets = parseTargets(targetSpecifiers, Fail); const doneP = Promise.allSettled( targets.map(async ({ boardID, instanceKitLabel }) => { @@ -81,9 +86,14 @@ export const upgradeGovernors = async ( Fail`${q(logLabel)} unexpected instanceKit label, got ${label} but wanted ${q(instanceKitLabel)}`; (adminFacet && adminFacet !== governorAdminFacet) || Fail`${q(logLabel)} instanceKit adminFacet should have been present and different from governorAdminFacet but was ${adminFacet}`; - const reason = harden(Error(`core-eval terminating ${label} governor`)); - await E(governorAdminFacet).upgradeContract(governorExecutorBundle, undefined); + await E(governorAdminFacet).upgradeContract( + governorExecutorBundleId, + undefined, + ); console.log(`${q(logLabel)} terminated governor`); + console.log('shutting down executor governor'); + const reason = harden(Error(`core-eval terminating ${label} governor`)); + await E(governorAdminFacet).terminateContract(reason); }), ); const results = await doneP; @@ -137,7 +147,7 @@ export const getManifest = async ( }, }, installations: { governorExecutor: restoreRef(bundleRef) }, // do we need installations ??? - options: { governorExecutorBundle: bundleRef, targetSpecifiers }, + options: { governorExecutorBundleId: bundleRef.bundleID, targetSpecifiers }, }; }; @@ -167,7 +177,10 @@ export default async (homeP, endowments) => { // import { makeHelpers } from '@agoric/deploy-script-support'; /** @type {import('@agoric/deploy-script-support/src/externalTypes.js').CoreEvalBuilder} */ -export const defaultProposalBuilder = async ({ publishRef, install }, targetSpecifiers) => { +export const defaultProposalBuilder = async ( + { publishRef, install }, + targetSpecifiers, +) => { parseTargets(targetSpecifiers); return harden({ sourceSpec: SELF, @@ -175,15 +188,13 @@ export const defaultProposalBuilder = async ({ publishRef, install }, targetSpec getManifest.name, { bundleRef: publishRef( - install( - '@agoric/governance/bundles/contractGovernorExecutor.js', - ), + install('@agoric/governance/src/contractGovernorExecutor.js'), ), targetSpecifiers, }, ], }); -} +}; /* @type {import('@agoric/deploy-script-support/src/externalTypes.js').DeployScriptFunction} */ /* @@ -203,4 +214,4 @@ export default async (homeP, endowments) => { await writeCoreEval(upgradeGovernors.name, utils => defaultProposalBuilder(utils, scriptArgs), ); -}; \ No newline at end of file +}; diff --git a/packages/governance/src/contractGovernorExecutor.js b/packages/governance/src/contractGovernorExecutor.js index 5ca9c73617a..733604d4dc1 100644 --- a/packages/governance/src/contractGovernorExecutor.js +++ b/packages/governance/src/contractGovernorExecutor.js @@ -1,5 +1,6 @@ import { E } from '@endo/eventual-send'; import { makeTracer } from '@agoric/internal'; +import { prepareExoClassKit } from '@agoric/vat-data'; const trace = makeTracer('CGExec', false); @@ -18,9 +19,42 @@ harden(meta); */ export const start = async (zcf, _privateArgs, baggage) => { trace('start'); - const contractInstanceAdminFacet = baggage.get('creatorFacet'); - const terminationData = harden(Error(`termination of contract by executor governor`)); - await E(contractInstanceAdminFacet).terminateContract(terminationData); - zcf.shutdown(`self-termination of executor governor`); + const makeContractGovernorKit = prepareExoClassKit( + baggage, + 'ContractGovernorKit', + undefined, + () => { + /** @type {Awaited>} */ + // @ts-expect-error + const kit = null; + return kit; + }, + { + creator: { + terminateInstance() { + const { adminFacet: contractInstanceAdminFacet } = this.state; + const terminationData = harden( + Error(`termination of contract by executor governor`), + ); + // we can't await remote calls in our 1st crank + // so fire-and-forget + void E(contractInstanceAdminFacet) + .terminateContract(terminationData) + .catch(err => { + console.log('FYI:', err); + }); + console.log('initiated termination of instance'); // TODO: what instance? + }, + }, + helper: {}, + public: {}, + }, + ); + console.log('defined ContractGovernorKit kind', makeContractGovernorKit); + /** @type {ReturnType} */ + const governorKit = baggage.get('contractGovernorKit'); + await governorKit.creator.terminateInstance(); + + return { creatorFacet: governorKit.creator, publicFacet: governorKit.public }; }; harden(start); From be62cb60763e2cfc66726d6e4476ee997dce9310 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Thu, 9 Jan 2025 12:03:03 -0600 Subject: [PATCH 3/9] test: terminate contract given boardID not via contract governor --- .../bootstrapTests/execute-governed.test.ts | 29 ++++++++++++++++++- .../scripts/vats/terminate-from-boardid.js | 22 ++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 packages/builders/scripts/vats/terminate-from-boardid.js diff --git a/packages/boot/test/bootstrapTests/execute-governed.test.ts b/packages/boot/test/bootstrapTests/execute-governed.test.ts index 2ed0f6a7617..3a94da8c076 100644 --- a/packages/boot/test/bootstrapTests/execute-governed.test.ts +++ b/packages/boot/test/bootstrapTests/execute-governed.test.ts @@ -1,20 +1,47 @@ +import type { BridgeHandler } from '@agoric/vats'; import { test as anyTest } from '@agoric/zoe/tools/prepare-test-env-ava.js'; import type { TestFn } from 'ava'; +import { readFile } from 'node:fs/promises'; +import { createRequire } from 'node:module'; import { makeWalletFactoryContext, type WalletFactoryTestContext, } from './walletFactory.js'; +const nodeRequire = createRequire(import.meta.url); +const asset = async (specifier: string) => + readFile(nodeRequire.resolve(specifier), 'utf8'); + // swingSetTestKit is the part we're really interested in const test: TestFn = anyTest; +type CoreEval = { json_permits: string; js_code: string }; test.before('bootstrap', async t => { const config = '@agoric/vm-config/decentral-itest-orchestration-config.json'; t.context = await makeWalletFactoryContext(t, config, {}); }); test.after.always(t => t.context.shutdown?.()); -test.serial('terminate a bad auctioneer & its governor', async t => { +test.serial('terminate BLAH BLAH', async t => { + const { swingsetTestKit } = t.context; + const runCoreEvals = async (evals: CoreEval[]) => { + const bridgeMessage = { type: 'CORE_EVAL', evals }; + const { EV } = swingsetTestKit.runUtils; + const coreEvalBridgeHandler: BridgeHandler = await EV.vat( + 'bootstrap', + ).consumeItem('coreEvalBridgeHandler'); + await EV(coreEvalBridgeHandler).fromBridge(bridgeMessage); + }; + + const script = await asset( + '@agoric/builders/scripts/vats/terminate-from-boardid.js', + ); + await runCoreEvals([{ js_code: script, json_permits: 'true' }]); + t.log('TODO: verify that the contract is gone'); + t.pass(); +}); + +test.skip('terminate a bad auctioneer & its governor', async t => { const { buildProposal, evalProposal } = t.context; const targets = [ diff --git a/packages/builders/scripts/vats/terminate-from-boardid.js b/packages/builders/scripts/vats/terminate-from-boardid.js new file mode 100644 index 00000000000..ec04e067da8 --- /dev/null +++ b/packages/builders/scripts/vats/terminate-from-boardid.js @@ -0,0 +1,22 @@ +// @ts-nocheck +/* global E */ +// import { E } from '@endo/far'; // TODO: remove + +const terminateFromBoardId = async powers => { + // const boardID = 'board04149'; // emerynet or something + const boardID = 'board03040'; // ATOM-USD_price_feed in bootstrap test + const instanceHandle = await E(powers.consume.board).getValue(boardID); + const instanceKit = await E(powers.consume.governedContractKits).get( + instanceHandle, + ); + console.log(instanceKit); + const instanceAdminFacet = await E( + instanceKit.governorCreatorFacet, + ).getAdminFacet(); + await E(instanceAdminFacet).terminateContract( + harden(Error('terminated by core-eval')), + ); +}; +harden(terminateFromBoardId); + +terminateFromBoardId; From 1f83e7ca578ab5e9f4e34d3c6e9a55761f08042e Mon Sep 17 00:00:00 2001 From: Siarhei Liakh Date: Wed, 15 Jan 2025 10:28:04 -0500 Subject: [PATCH 4/9] add terminate-governed.test.ts test refs #10725 --- .../bootstrapTests/terminate-governed.test.ts | 201 ++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 packages/boot/test/bootstrapTests/terminate-governed.test.ts diff --git a/packages/boot/test/bootstrapTests/terminate-governed.test.ts b/packages/boot/test/bootstrapTests/terminate-governed.test.ts new file mode 100644 index 00000000000..fd779b53dd8 --- /dev/null +++ b/packages/boot/test/bootstrapTests/terminate-governed.test.ts @@ -0,0 +1,201 @@ +import { test as anyTest } from '@agoric/swingset-vat/tools/prepare-test-env-ava.js'; +import type { TestFn } from 'ava'; +import path from 'path'; +import bundleSource from '@endo/bundle-source'; +import { CONTRACT_ELECTORATE, ParamTypes } from '@agoric/governance'; +import { MALLEABLE_NUMBER } from '@agoric/governance/test/swingsetTests/contractGovernor/governedContract.js'; +import { makeAgoricNamesRemotesFromFakeStorage } from '@agoric/vats/tools/board-utils.js'; + +import { makeSwingsetTestKit } from '../../tools/supports.js'; +import { + makeGovernanceDriver, + makeWalletFactoryDriver, +} from '../../tools/drivers.js'; + +const dirname = path.dirname(new URL(import.meta.url).pathname); + +const GOVERNED_CONTRACT_SRC = '../../../governance/test/swingsetTests/contractGovernor/governedContract.js'; + +const setUpGovernedContract = async (zoe, timer, EV, controller) => { + const installBundle = contractBundle => EV(zoe).install(contractBundle); + //const installBundleToVatAdmin = contractBundle => + // controller.validateAndInstallBundle(contractBundle); + const source = `${dirname}/${GOVERNED_CONTRACT_SRC}`; + const governedContractBundle = await bundleSource(source); + + const agoricNames = await EV.vat('bootstrap').consumeItem('agoricNames'); + const governorInstallation = await EV(agoricNames).lookup( + 'installation', + 'contractGovernor', + ); + const voteCounterInstallation = await EV(agoricNames).lookup( + 'installation', + 'binaryVoteCounter', + ); + + const electorateCreatorFacet = await EV.vat('bootstrap').consumeItem( + 'economicCommitteeCreatorFacet', + ); + const poserInvitation = await EV(electorateCreatorFacet).getPoserInvitation(); + const poserInvitation2 = await EV( + electorateCreatorFacet, + ).getPoserInvitation(); + + const invitationIssuer = await EV(zoe).getInvitationIssuer(); + const invitationAmount = + await EV(invitationIssuer).getAmountOf(poserInvitation); + + const governedTerms = { + governedParams: { + [MALLEABLE_NUMBER]: { + type: ParamTypes.NAT, + value: 602214090000000000000000n, + }, + [CONTRACT_ELECTORATE]: { + type: ParamTypes.INVITATION, + value: invitationAmount, + }, + }, + }; + + const governedInstallation = await installBundle(governedContractBundle); + const governorTerms = { + timer, + governedContractInstallation: governedInstallation, + governed: { + terms: governedTerms, + issuerKeywordRecord: {}, + }, + }; + + const governorFacets = await EV(zoe).startInstance( + governorInstallation, + {}, + governorTerms, + { + governed: { + initialPoserInvitation: poserInvitation, + }, + }, + ); + + return { + governorFacets, + invitationAmount, + voteCounterInstallation, + poserInvitation2, + }; +}; + +// A more minimal set would be better. We need governance, but not econ vats. +const PLATFORM_CONFIG = '@agoric/vm-config/decentral-main-vaults-config.json'; + +const makeDefaultTestContext = async t => { + console.time('DefaultTestContext'); + const swingsetTestKit = await makeSwingsetTestKit(t.log, undefined, { + configSpecifier: PLATFORM_CONFIG, + }); + + const { runUtils, storage, controller } = swingsetTestKit; + console.timeLog('DefaultTestContext', 'swingsetTestKit'); + const { EV } = runUtils; + const zoe: ZoeService = await EV.vat('bootstrap').consumeItem('zoe'); + const timer = await EV.vat('bootstrap').consumeItem('chainTimerService'); + + // has to be late enough for agoricNames data to have been published + const agoricNamesRemotes = makeAgoricNamesRemotesFromFakeStorage(storage); + + const walletFactoryDriver = await makeWalletFactoryDriver( + runUtils, + storage, + agoricNamesRemotes, + ); + + const governanceDriver = await makeGovernanceDriver( + swingsetTestKit, + agoricNamesRemotes, + walletFactoryDriver, + [], + ); + + const facets = await setUpGovernedContract(zoe, timer, EV, controller); + + return { ...swingsetTestKit, facets, governanceDriver }; +}; + +const test = anyTest as TestFn< + Awaited> +>; + +test.before(async t => { + t.context = await makeDefaultTestContext(t); +}); + +test.after.always(t => { + return t.context.shutdown && t.context.shutdown(); +}); + +/* +test(`start contract; verify`, async t => { + const { runUtils, facets } = t.context; + const { + governorFacets: { creatorFacet }, + } = facets; + const { EV } = runUtils; + const contractPublicFacet = await EV(creatorFacet).getPublicFacet(); + + const avogadro = await EV(contractPublicFacet).getNum(); + t.is(await EV(contractPublicFacet).getApiCalled(), 0); + t.is(avogadro, 602214090000000000000000n); +}); +*/ + +test(`Create a contract and acquire a boardId`, async t => { + const { runUtils, facets, governanceDriver, storage } = t.context; + const { + governorFacets: { creatorFacet, instance, adminFacet }, // this is governor's adminFacet + voteCounterInstallation: vci, + } = facets; + + const { EV } = runUtils; + const contractPublicFacet = await EV(creatorFacet).getPublicFacet(); + + // set a cookie value in the contract + const avogadro = await EV(contractPublicFacet).getNum(); + t.is(await EV(contractPublicFacet).getApiCalled(), 0); + t.is(avogadro, 602214090000000000000000n); + + //const committee = governanceDriver.ecMembers; + + const agoricNamesAdmin = + await EV.vat('bootstrap').consumeItem('agoricNamesAdmin'); + // await EV(agoricNamesAdmin).reserve('governedContract'); + const instanceAdmin = await EV(agoricNamesAdmin).lookupAdmin('instance'); + console.log('UPGA ', { instanceAdmin }); + await EV(instanceAdmin).update('governedContract', instance); + const agoricNamesRemotes = makeAgoricNamesRemotesFromFakeStorage(storage); + + const { governedContract } = agoricNamesRemotes.instance; + //const board = await EV.vat('bootstrap').consumeItem('board'); + const boardId = await governedContract.getBoardId(); + + console.log({ boardId }); + // t.context.boardId = boardId; + + // verify the contract works and the cookie matches + const num = await EV(contractPublicFacet).getNum(); + t.is(num, 602214090000000000000000n); + + // terminate -- replace with a coreEval + console.log({ adminFacet }); + const contractAdminFacet = await EV(creatorFacet).getAdminFacet(); + await EV(contractAdminFacet).terminateContract(Error('Reasons')); + + // confirm the contract is no longer there + await t.throwsAsync( + () => EV(contractPublicFacet).getNum(), + { + message: 'vat terminated', + } + ); +}); \ No newline at end of file From a0849f32b32b7a9c6de43331b598b40cad36a465 Mon Sep 17 00:00:00 2001 From: Siarhei Liakh Date: Fri, 17 Jan 2025 17:29:16 -0500 Subject: [PATCH 5/9] feat(bilders): add terminate-governed-instance.js proposal refs: #10725 This change includes: * A single-file core-eval builder-proposal to terminate governed contracts and their governors by boardID. * A bootstrap test which: ** creates an instance of a simple governed contract via its own core-eval ** confirms that the contract accessible and operational ** kill the contract instance by boardID ** confirms that the contract is no longer operational --- .../bootstrapTests/terminate-governed.test.ts | 189 +++-------------- .../vats/terminate-governed-instance.js | 147 +++++++++++++ packages/governance/package.json | 5 +- .../contractGovernor/add-governedContract.js | 198 ++++++++++++++++++ .../contractGovernor/governedContract.js | 5 +- packages/internal/src/module-utils.js | 23 ++ 6 files changed, 405 insertions(+), 162 deletions(-) create mode 100644 packages/builders/scripts/vats/terminate-governed-instance.js create mode 100644 packages/governance/test/swingsetTests/contractGovernor/add-governedContract.js create mode 100644 packages/internal/src/module-utils.js diff --git a/packages/boot/test/bootstrapTests/terminate-governed.test.ts b/packages/boot/test/bootstrapTests/terminate-governed.test.ts index fd779b53dd8..dbfc7df67c6 100644 --- a/packages/boot/test/bootstrapTests/terminate-governed.test.ts +++ b/packages/boot/test/bootstrapTests/terminate-governed.test.ts @@ -1,126 +1,22 @@ import { test as anyTest } from '@agoric/swingset-vat/tools/prepare-test-env-ava.js'; import type { TestFn } from 'ava'; -import path from 'path'; -import bundleSource from '@endo/bundle-source'; -import { CONTRACT_ELECTORATE, ParamTypes } from '@agoric/governance'; -import { MALLEABLE_NUMBER } from '@agoric/governance/test/swingsetTests/contractGovernor/governedContract.js'; -import { makeAgoricNamesRemotesFromFakeStorage } from '@agoric/vats/tools/board-utils.js'; - import { makeSwingsetTestKit } from '../../tools/supports.js'; -import { - makeGovernanceDriver, - makeWalletFactoryDriver, -} from '../../tools/drivers.js'; - -const dirname = path.dirname(new URL(import.meta.url).pathname); - -const GOVERNED_CONTRACT_SRC = '../../../governance/test/swingsetTests/contractGovernor/governedContract.js'; - -const setUpGovernedContract = async (zoe, timer, EV, controller) => { - const installBundle = contractBundle => EV(zoe).install(contractBundle); - //const installBundleToVatAdmin = contractBundle => - // controller.validateAndInstallBundle(contractBundle); - const source = `${dirname}/${GOVERNED_CONTRACT_SRC}`; - const governedContractBundle = await bundleSource(source); - - const agoricNames = await EV.vat('bootstrap').consumeItem('agoricNames'); - const governorInstallation = await EV(agoricNames).lookup( - 'installation', - 'contractGovernor', - ); - const voteCounterInstallation = await EV(agoricNames).lookup( - 'installation', - 'binaryVoteCounter', - ); - - const electorateCreatorFacet = await EV.vat('bootstrap').consumeItem( - 'economicCommitteeCreatorFacet', - ); - const poserInvitation = await EV(electorateCreatorFacet).getPoserInvitation(); - const poserInvitation2 = await EV( - electorateCreatorFacet, - ).getPoserInvitation(); - - const invitationIssuer = await EV(zoe).getInvitationIssuer(); - const invitationAmount = - await EV(invitationIssuer).getAmountOf(poserInvitation); - - const governedTerms = { - governedParams: { - [MALLEABLE_NUMBER]: { - type: ParamTypes.NAT, - value: 602214090000000000000000n, - }, - [CONTRACT_ELECTORATE]: { - type: ParamTypes.INVITATION, - value: invitationAmount, - }, - }, - }; - - const governedInstallation = await installBundle(governedContractBundle); - const governorTerms = { - timer, - governedContractInstallation: governedInstallation, - governed: { - terms: governedTerms, - issuerKeywordRecord: {}, - }, - }; - - const governorFacets = await EV(zoe).startInstance( - governorInstallation, - {}, - governorTerms, - { - governed: { - initialPoserInvitation: poserInvitation, - }, - }, - ); - - return { - governorFacets, - invitationAmount, - voteCounterInstallation, - poserInvitation2, - }; -}; // A more minimal set would be better. We need governance, but not econ vats. const PLATFORM_CONFIG = '@agoric/vm-config/decentral-main-vaults-config.json'; const makeDefaultTestContext = async t => { - console.time('DefaultTestContext'); const swingsetTestKit = await makeSwingsetTestKit(t.log, undefined, { configSpecifier: PLATFORM_CONFIG, }); - - const { runUtils, storage, controller } = swingsetTestKit; - console.timeLog('DefaultTestContext', 'swingsetTestKit'); + const { runUtils } = swingsetTestKit; const { EV } = runUtils; - const zoe: ZoeService = await EV.vat('bootstrap').consumeItem('zoe'); - const timer = await EV.vat('bootstrap').consumeItem('chainTimerService'); - - // has to be late enough for agoricNames data to have been published - const agoricNamesRemotes = makeAgoricNamesRemotesFromFakeStorage(storage); - const walletFactoryDriver = await makeWalletFactoryDriver( - runUtils, - storage, - agoricNamesRemotes, - ); - - const governanceDriver = await makeGovernanceDriver( - swingsetTestKit, - agoricNamesRemotes, - walletFactoryDriver, - [], - ); - - const facets = await setUpGovernedContract(zoe, timer, EV, controller); + // We need to poke at bootstrap vat and wait for results to allow + // SwingSet to finish its boot process before we start the test + const zoe: ZoeService = await EV.vat('bootstrap').consumeItem('zoe'); - return { ...swingsetTestKit, facets, governanceDriver }; + return { ...swingsetTestKit }; }; const test = anyTest as TestFn< @@ -135,65 +31,42 @@ test.after.always(t => { return t.context.shutdown && t.context.shutdown(); }); -/* -test(`start contract; verify`, async t => { - const { runUtils, facets } = t.context; - const { - governorFacets: { creatorFacet }, - } = facets; - const { EV } = runUtils; - const contractPublicFacet = await EV(creatorFacet).getPublicFacet(); - - const avogadro = await EV(contractPublicFacet).getNum(); - t.is(await EV(contractPublicFacet).getApiCalled(), 0); - t.is(avogadro, 602214090000000000000000n); -}); -*/ - -test(`Create a contract and acquire a boardId`, async t => { - const { runUtils, facets, governanceDriver, storage } = t.context; - const { - governorFacets: { creatorFacet, instance, adminFacet }, // this is governor's adminFacet - voteCounterInstallation: vci, - } = facets; +test(`Create a contract via core-eval and kill it via core-eval by boardID `, async t => { + const TEST_CONTRACT_LABEL = 'testContractLabel'; + const { runUtils, buildProposal, evalProposal } = t.context; const { EV } = runUtils; - const contractPublicFacet = await EV(creatorFacet).getPublicFacet(); - - // set a cookie value in the contract - const avogadro = await EV(contractPublicFacet).getNum(); - t.is(await EV(contractPublicFacet).getApiCalled(), 0); - t.is(avogadro, 602214090000000000000000n); - - //const committee = governanceDriver.ecMembers; - const agoricNamesAdmin = - await EV.vat('bootstrap').consumeItem('agoricNamesAdmin'); - // await EV(agoricNamesAdmin).reserve('governedContract'); - const instanceAdmin = await EV(agoricNamesAdmin).lookupAdmin('instance'); - console.log('UPGA ', { instanceAdmin }); - await EV(instanceAdmin).update('governedContract', instance); - const agoricNamesRemotes = makeAgoricNamesRemotesFromFakeStorage(storage); - - const { governedContract } = agoricNamesRemotes.instance; - //const board = await EV.vat('bootstrap').consumeItem('board'); - const boardId = await governedContract.getBoardId(); + // create a contract via core-eval + const testContractProposalArgs = [TEST_CONTRACT_LABEL,]; + const creatorProposalMaterials = buildProposal( + '@agoric/governance/test/swingsetTests/contractGovernor/add-governedContract.js', + testContractProposalArgs, + ); + await evalProposal(creatorProposalMaterials); - console.log({ boardId }); - // t.context.boardId = boardId; + const { boardID, publicFacet } = await EV.vat('bootstrap').consumeItem(TEST_CONTRACT_LABEL); + console.log({ boardID }); - // verify the contract works and the cookie matches - const num = await EV(contractPublicFacet).getNum(); + // confirming the contract actually works + const num = await EV(publicFacet).getNum(); t.is(num, 602214090000000000000000n); - // terminate -- replace with a coreEval - console.log({ adminFacet }); - const contractAdminFacet = await EV(creatorFacet).getAdminFacet(); - await EV(contractAdminFacet).terminateContract(Error('Reasons')); + // killing via terminate-governed-instance + const targets = [ + `${boardID}:${TEST_CONTRACT_LABEL}`, + ]; + console.log({ targets }); + + const terminatorProposalMaterials = buildProposal( + '@agoric/builders/scripts/vats/terminate-governed-instance.js', + targets, + ); + await evalProposal(terminatorProposalMaterials); // confirm the contract is no longer there await t.throwsAsync( - () => EV(contractPublicFacet).getNum(), + () => EV(publicFacet).getNum(), { message: 'vat terminated', } diff --git a/packages/builders/scripts/vats/terminate-governed-instance.js b/packages/builders/scripts/vats/terminate-governed-instance.js new file mode 100644 index 00000000000..52a806f1b9a --- /dev/null +++ b/packages/builders/scripts/vats/terminate-governed-instance.js @@ -0,0 +1,147 @@ +/** + * @file Terminate price-feed governor instances such as mainnet v110. + * Functions as both an off-chain builder and an on-chain core-eval. + */ + +/// + +import { E } from '@endo/far'; + +const SELF = '@agoric/builders/scripts/vats/terminate-governed-instance.js'; +const USAGE = `Usage: agoric run /path/to/terminate-governed-instance.js \\ + <$governorInstanceHandleBoardID:$instanceKitLabel>...`; + +const repr = val => + typeof val === 'string' || (typeof val === 'object' && val !== null) + ? JSON.stringify(val) + : String(val); +const defaultMakeError = (strings, ...subs) => + Error( + strings.map((s, i) => `${i === 0 ? '' : repr(subs[i - 1])}${s}`).join(''), + ); +const makeUsageError = (strings, ...subs) => { + const err = defaultMakeError(strings, ...subs); + console.error(err.message); + console.error(USAGE); + return err; +}; + +const rtarget = /^(?board[0-9]+):(?.+)$/; +/** + * @param {string[]} args + * @param {(strings: TemplateStringsArray | string[], ...subs: unknown[]) => Error} [makeError] + * @returns {Array<{boardID: string, instanceKitLabel: string}>} + */ +const parseTargets = (args = [], makeError = defaultMakeError) => { + if (!Array.isArray(args)) throw makeError`invalid targets: ${args}`; + /** @type {Array<{boardID: string, instanceKitLabel: string}>} */ + const targets = []; + const badTargets = []; + for (const arg of args) { + const m = typeof arg === 'string' && arg.match(rtarget); + if (!m) { + badTargets.push(arg); + } else { + // @ts-expect-error cast + targets.push(m.groups); + } + } + if (badTargets.length !== 0) { + throw makeError`malformed target(s): ${badTargets}`; + } else if (targets.length === 0) { + throw makeError`no target(s)`; + } + return targets; +}; + +/** + * @param {BootstrapPowers} powers + * @param {{ options: { targetSpecifiers: string[] } }} config + */ +export const terminateGoverned = async ( + { consume: { board, governedContractKits } }, + { options: { targetSpecifiers } }, +) => { + const { Fail, quote: q } = assert; + const targets = parseTargets(targetSpecifiers, Fail); + const doneP = Promise.allSettled( + targets.map(async ({ boardID, instanceKitLabel }) => { + const logLabel = [boardID, instanceKitLabel]; + const contractInstanceHandle = await E(board).getValue(boardID); + const instanceKit = await E(governedContractKits).get( + // @ts-expect-error TS2345 Property '[tag]' is missing + contractInstanceHandle, + ); + console.log( + `${q(logLabel)} alleged governor contract instance kit`, + instanceKit, + ); + + const instanceAdminFacet = await E( + instanceKit.governorCreatorFacet, + ).getAdminFacet(); + + const { label, governorAdminFacet, adminFacet } = instanceKit; + label === instanceKitLabel || + Fail`${q(logLabel)} unexpected instanceKit label, got ${label} but wanted ${q(instanceKitLabel)}`; + (adminFacet && adminFacet !== governorAdminFacet) || + Fail`${q(logLabel)} instanceKit adminFacet should have been present and different from governorAdminFacet but was ${adminFacet}`; + + console.log({ instanceAdminFacet }); + + await E(instanceAdminFacet).terminateContract( + harden(Error('Governed contract terminated by core-eval')), + ); + console.log('Governed contract terminated by core-eval.'); + + const reason = harden(Error(`core-eval terminating ${label} governor`)); + await E(governorAdminFacet).terminateContract(reason); + console.log(`${q(logLabel)} terminated governor`); + }), + ); + const results = await doneP; + const problems = targets.flatMap(({ boardID, instanceKitLabel }, i) => { + if (results[i].status === 'fulfilled') return []; + return [[boardID, instanceKitLabel, results[i].reason]]; + }); + if (problems.length !== 0) { + console.error('governor termination(s) failed', problems); + Fail`governor termination(s) failed: ${problems}`; + } +}; +harden(terminateGoverned); + +export const getManifest = (_powers, targetSpecifiers) => { + parseTargets(targetSpecifiers); + return { + manifest: { + [terminateGoverned.name]: { + consume: { board: true, governedContractKits: true }, + }, + }, + // Provide `terminateGovernors` a second argument like + // `{ options: { targetSpecifiers } }`. + options: { targetSpecifiers }, + }; +}; + +/** @type {import('@agoric/deploy-script-support/src/externalTypes.js').CoreEvalBuilder} */ +export const defaultProposalBuilder = async (_utils, targetSpecifiers) => { + parseTargets(targetSpecifiers); + return harden({ + sourceSpec: SELF, + getManifestCall: ['getManifest', targetSpecifiers], + }); +}; + +/** @type {import('@agoric/deploy-script-support/src/externalTypes.js').DeployScriptFunction} */ +export default async (homeP, endowments) => { + const { scriptArgs } = endowments; + parseTargets(scriptArgs, makeUsageError); + const dspModule = await import('@agoric/deploy-script-support'); + const { makeHelpers } = dspModule; + const { writeCoreEval } = await makeHelpers(homeP, endowments); + await writeCoreEval(terminateGoverned.name, utils => + defaultProposalBuilder(utils, scriptArgs), + ); +}; diff --git a/packages/governance/package.json b/packages/governance/package.json index cd793427660..c794f4c1455 100644 --- a/packages/governance/package.json +++ b/packages/governance/package.json @@ -53,7 +53,8 @@ "@endo/bundle-source": "^3.5.0", "@endo/init": "^1.1.7", "ava": "^5.3.0", - "c8": "^10.1.2" + "c8": "^10.1.2", + "@endo/compartment-mapper": "^1.4.0" }, "files": [ "README.md", @@ -78,4 +79,4 @@ "typeCoverage": { "atLeast": 89.49 } -} +} \ No newline at end of file diff --git a/packages/governance/test/swingsetTests/contractGovernor/add-governedContract.js b/packages/governance/test/swingsetTests/contractGovernor/add-governedContract.js new file mode 100644 index 00000000000..04ceea1eb92 --- /dev/null +++ b/packages/governance/test/swingsetTests/contractGovernor/add-governedContract.js @@ -0,0 +1,198 @@ +// import { makeHelpers } from '@agoric/deploy-script-support'; +import { deeplyFulfilledObject, makeTracer } from '@agoric/internal'; +import { E } from '@endo/far'; + +import { CONTRACT_ELECTORATE, ParamTypes } from '@agoric/governance'; +import { MALLEABLE_NUMBER } from '@agoric/governance/test/swingsetTests/contractGovernor/governedContract.js'; + +const trace = makeTracer('GovC-coreEval', true); + +/** + * @typedef {PromiseSpaceOf<{ + * auctionUpgradeNewInstance: Instance; + * auctionUpgradeNewGovCreator: any; + * newContractGovBundleId: string; + * retiredContractInstances: MapStore; + * }>} interlockPowers + */ + +/** + * @param {import('@agoric/inter-protocol/src/proposals/econ-behaviors.js').EconomyBootstrapPowers & + * interlockPowers} powers + * @param {{ + * options: { + * contractInstallation: Installation; + * producedKey: string | undefined; + * label: string | undefined; + * }; + * }} options + */ +export const startGovernedInstance = async ( + { + consume: { + agoricNames, + board, + chainTimerService: chainTimerServiceP, + economicCommitteeCreatorFacet: electorateCreatorFacet, + governedContractKits: governedContractKitsP, + zoe, + }, + produce, + }, + { + options: { + contractInstallation, + producedKey, + label = 'governedContract', + }, + }, +) => { + trace('Governed Contract start'); + + const poserInvitationP = E(electorateCreatorFacet).getPoserInvitation(); + const [ + chainTimerService, + poserInvitation, + governedContractKits, + ] = await Promise.all([ + chainTimerServiceP, + poserInvitationP, + governedContractKitsP, + ]); + + const invitationIssuer = await E(zoe).getInvitationIssuer(); + const invitationAmount = + await E(invitationIssuer).getAmountOf(poserInvitation); + + const governedTerms = { + governedParams: { + [MALLEABLE_NUMBER]: { + type: ParamTypes.NAT, + value: 602214090000000000000000n, + }, + [CONTRACT_ELECTORATE]: { + type: ParamTypes.INVITATION, + value: invitationAmount, + }, + }, + }; + + const governorTerms = { + chainTimerService, + governedContractInstallation: contractInstallation, + governed: { + terms: governedTerms, + issuerKeywordRecord: {}, + label, + }, + }; + + const governorInstallation = await E(agoricNames).lookup( + 'installation', + 'contractGovernor', + ); + + const governorStartResult = await E(zoe).startInstance( + governorInstallation, + {}, + governorTerms, + { + //electorateCreatorFacet, + governed: { + initialPoserInvitation: poserInvitation, + }, + }, + `${label}.governor`, + ); + + const kit = await deeplyFulfilledObject(harden({ + label, + creatorFacet: E(governorStartResult.creatorFacet).getCreatorFacet(), + adminFacet: E(governorStartResult.creatorFacet).getAdminFacet(), + publicFacet: E(governorStartResult.creatorFacet).getPublicFacet(), + instance: E(governorStartResult.creatorFacet).getInstance(), + + governor: governorStartResult.instance, + governorCreatorFacet: governorStartResult.creatorFacet, + governorAdminFacet: governorStartResult.adminFacet, + })); + + governedContractKits.init(kit.instance, kit); + + if (producedKey !== undefined) { + const boardID = await E(board).getId(kit.instance); + trace({ boardID }); + produce[producedKey].reset(); + produce[producedKey].resolve({ ...kit, boardID }); + } +}; + +/** + * Add a new auction to a chain that already has one. + * + * @param {object} utils + * @param {any} utils.restoreRef + * @param {any} addAuctionOptions + */ +export const getManifestForGovernedContract = async ( + { restoreRef }, + { contractBundleRef, producedKey, label }, +) => { + const contractInstallation = restoreRef(contractBundleRef); + return { + manifest: { + [startGovernedInstance.name]: { + consume: { + agoricNames: true, + board: true, + chainTimerService: true, + economicCommitteeCreatorFacet: true, + governedContractKits: true, + zoe: true, + }, + produce: producedKey === undefined ? {} : { [producedKey]: true }, + }, + }, + // XXX we should be able to receive contractGovernorInstallation via + // installations.consume, but the received installation isn't right. + options: { contractInstallation, producedKey, label }, + }; +}; + +/** @type {import('@agoric/deploy-script-support/src/externalTypes.js').CoreEvalBuilder} */ +export const defaultProposalBuilder = async ({ publishRef, install }, [producedKey, label = producedKey] = []) => { + const { getSpecifier } = await import('@agoric/internal/src/module-utils.js'); + const SELF = await getSpecifier(import.meta.url); + + return harden({ + sourceSpec: SELF, + getManifestCall: [ + getManifestForGovernedContract.name, + { + contractBundleRef: publishRef( + install( + '@agoric/governance/test/swingsetTests/contractGovernor/governedContract.js', + ), + ), + governorBundleRef: publishRef( + install( + '@agoric/governance/src/contractGovernor.js', + ), + ), + producedKey, + label, + }, + ], + }); +}; + +/** @type {import('@agoric/deploy-script-support/src/externalTypes.js').DeployScriptFunction} */ +export default async (homeP, endowments) => { + const { scriptArgs } = endowments; + const dspModule = await import('@agoric/deploy-script-support'); + const { makeHelpers } = dspModule; + const { writeCoreEval } = await makeHelpers(homeP, endowments); + await writeCoreEval(startGovernedInstance.name, utils => + defaultProposalBuilder(utils, scriptArgs), + ); +}; diff --git a/packages/governance/test/swingsetTests/contractGovernor/governedContract.js b/packages/governance/test/swingsetTests/contractGovernor/governedContract.js index b0231c0c78e..e9c2cc4e5aa 100644 --- a/packages/governance/test/swingsetTests/contractGovernor/governedContract.js +++ b/packages/governance/test/swingsetTests/contractGovernor/governedContract.js @@ -1,6 +1,7 @@ import { handleParamGovernance } from '../../../src/contractHelper.js'; import { ParamTypes } from '../../../src/index.js'; import { CONTRACT_ELECTORATE } from '../../../src/contractGovernance/governParam.js'; +import { Far } from '@endo/marshal'; /** * @import {GovernanceTerms} from '../../../src/types.js'; @@ -29,7 +30,7 @@ const makeTerms = (number, invitationAmount) => { * @param {{initialPoserInvitation: Invitation}} privateArgs */ const start = async (zcf, privateArgs) => { - const { augmentPublicFacet, makeGovernorFacet, params } = + const { augmentPublicFacet, makeFarGovernorFacet, params } = await handleParamGovernance(zcf, privateArgs.initialPoserInvitation, { [MALLEABLE_NUMBER]: ParamTypes.NAT, }); @@ -41,7 +42,7 @@ const start = async (zcf, privateArgs) => { getNum: () => params.getMalleableNumber(), getApiCalled: () => governanceAPICalled, }), - creatorFacet: makeGovernorFacet({}, { governanceApi }), + creatorFacet: makeFarGovernorFacet(Far('governedContract creatorFacet'), { governanceApi }), }; }; diff --git a/packages/internal/src/module-utils.js b/packages/internal/src/module-utils.js new file mode 100644 index 00000000000..5da942715d7 --- /dev/null +++ b/packages/internal/src/module-utils.js @@ -0,0 +1,23 @@ +import { readFile } from 'node:fs/promises'; +import { fileURLToPath } from 'node:url'; +import { search } from '@endo/compartment-mapper'; +import { Fail } from '@endo/errors'; + +/** + * Get a deep-import specifier for a file, starting with the specifier for its containing module. + * + * @param {string} fileUrl + * @returns {Promise} + */ +export const getSpecifier = async fileUrl => { + const read = async location => { + const path = fileURLToPath(new URL(location, fileUrl)); + return readFile(path); + }; + const searchResult = await search(read, fileUrl); + const { packageDescriptorText, moduleSpecifier } = searchResult; + const { name } = JSON.parse(packageDescriptorText); + name || Fail`file package has no name`; + return `${name}/${moduleSpecifier}`; +}; +harden(getSpecifier); \ No newline at end of file From 527ed5dca20ef43935ee4f6441c2ba37485ea5a8 Mon Sep 17 00:00:00 2001 From: Siarhei Liakh Date: Tue, 21 Jan 2025 16:33:47 -0500 Subject: [PATCH 6/9] cleanup: remove unneeded files and intermediate tests refs #10725 --- .../bootstrapTests/execute-governed.test.ts | 59 ----- .../scripts/vats/terminate-from-boardid.js | 22 -- .../scripts/vats/upgrade-governor-instance.js | 217 ------------------ .../src/contractGovernorExecutor.js | 60 ----- 4 files changed, 358 deletions(-) delete mode 100644 packages/boot/test/bootstrapTests/execute-governed.test.ts delete mode 100644 packages/builders/scripts/vats/terminate-from-boardid.js delete mode 100644 packages/builders/scripts/vats/upgrade-governor-instance.js delete mode 100644 packages/governance/src/contractGovernorExecutor.js diff --git a/packages/boot/test/bootstrapTests/execute-governed.test.ts b/packages/boot/test/bootstrapTests/execute-governed.test.ts deleted file mode 100644 index 3a94da8c076..00000000000 --- a/packages/boot/test/bootstrapTests/execute-governed.test.ts +++ /dev/null @@ -1,59 +0,0 @@ -import type { BridgeHandler } from '@agoric/vats'; -import { test as anyTest } from '@agoric/zoe/tools/prepare-test-env-ava.js'; -import type { TestFn } from 'ava'; -import { readFile } from 'node:fs/promises'; -import { createRequire } from 'node:module'; -import { - makeWalletFactoryContext, - type WalletFactoryTestContext, -} from './walletFactory.js'; - -const nodeRequire = createRequire(import.meta.url); -const asset = async (specifier: string) => - readFile(nodeRequire.resolve(specifier), 'utf8'); - -// swingSetTestKit is the part we're really interested in -const test: TestFn = anyTest; - -type CoreEval = { json_permits: string; js_code: string }; -test.before('bootstrap', async t => { - const config = '@agoric/vm-config/decentral-itest-orchestration-config.json'; - t.context = await makeWalletFactoryContext(t, config, {}); -}); -test.after.always(t => t.context.shutdown?.()); - -test.serial('terminate BLAH BLAH', async t => { - const { swingsetTestKit } = t.context; - const runCoreEvals = async (evals: CoreEval[]) => { - const bridgeMessage = { type: 'CORE_EVAL', evals }; - const { EV } = swingsetTestKit.runUtils; - const coreEvalBridgeHandler: BridgeHandler = await EV.vat( - 'bootstrap', - ).consumeItem('coreEvalBridgeHandler'); - await EV(coreEvalBridgeHandler).fromBridge(bridgeMessage); - }; - - const script = await asset( - '@agoric/builders/scripts/vats/terminate-from-boardid.js', - ); - await runCoreEvals([{ js_code: script, json_permits: 'true' }]); - t.log('TODO: verify that the contract is gone'); - t.pass(); -}); - -test.skip('terminate a bad auctioneer & its governor', async t => { - const { buildProposal, evalProposal } = t.context; - - const targets = [ - 'board03040:ATOM-USD_price_feed', // TODO: auctioneer - ]; - - const materials = buildProposal( - '@agoric/builders/scripts/vats/upgrade-governor-instance.js', - targets, - ); - await evalProposal(materials); - - t.log('TODO: check that the price feed is really dead'); - t.pass(); -}); diff --git a/packages/builders/scripts/vats/terminate-from-boardid.js b/packages/builders/scripts/vats/terminate-from-boardid.js deleted file mode 100644 index ec04e067da8..00000000000 --- a/packages/builders/scripts/vats/terminate-from-boardid.js +++ /dev/null @@ -1,22 +0,0 @@ -// @ts-nocheck -/* global E */ -// import { E } from '@endo/far'; // TODO: remove - -const terminateFromBoardId = async powers => { - // const boardID = 'board04149'; // emerynet or something - const boardID = 'board03040'; // ATOM-USD_price_feed in bootstrap test - const instanceHandle = await E(powers.consume.board).getValue(boardID); - const instanceKit = await E(powers.consume.governedContractKits).get( - instanceHandle, - ); - console.log(instanceKit); - const instanceAdminFacet = await E( - instanceKit.governorCreatorFacet, - ).getAdminFacet(); - await E(instanceAdminFacet).terminateContract( - harden(Error('terminated by core-eval')), - ); -}; -harden(terminateFromBoardId); - -terminateFromBoardId; diff --git a/packages/builders/scripts/vats/upgrade-governor-instance.js b/packages/builders/scripts/vats/upgrade-governor-instance.js deleted file mode 100644 index af4b8526100..00000000000 --- a/packages/builders/scripts/vats/upgrade-governor-instance.js +++ /dev/null @@ -1,217 +0,0 @@ -/** - * @file Upgrade price-feed governor instances such as emerynet v664. - * Functions as both an off-chain builder and an on-chain core-eval. - */ - -/// - -import { E } from '@endo/far'; - -const SELF = '@agoric/builders/scripts/vats/upgrade-governor-instance.js'; -const USAGE = `Usage: agoric run /path/to/upgrade-governor-instance.js \\ - <$governorInstanceHandleBoardID:$instanceKitLabel>...`; - -const repr = val => - typeof val === 'string' || (typeof val === 'object' && val !== null) - ? JSON.stringify(val) - : String(val); -const defaultMakeError = (strings, ...subs) => - Error( - strings.map((s, i) => `${i === 0 ? '' : repr(subs[i - 1])}${s}`).join(''), - ); -const makeUsageError = (strings, ...subs) => { - const err = defaultMakeError(strings, ...subs); - console.error(err.message); - console.error(USAGE); - return err; -}; - -const rtarget = /^(?board[0-9]+):(?.+)$/; -/** - * @param {string[]} args - * @param {(strings: TemplateStringsArray | string[], ...subs: unknown[]) => Error} [makeError] - * @returns {Array<{boardID: string, instanceKitLabel: string}>} - */ -const parseTargets = (args = [], makeError = defaultMakeError) => { - if (!Array.isArray(args)) throw makeError`invalid targets: ${args}`; - /** @type {Array<{boardID: string, instanceKitLabel: string}>} */ - const targets = []; - const badTargets = []; - for (const arg of args) { - const m = typeof arg === 'string' && arg.match(rtarget); - if (!m) { - badTargets.push(arg); - } else { - // @ts-expect-error cast - targets.push(m.groups); - } - } - if (badTargets.length !== 0) { - throw makeError`malformed target(s): ${badTargets}`; - } else if (targets.length === 0) { - throw makeError`no target(s)`; - } - return targets; -}; - -/** - * Given boardIDs of some instances of contract governors, - * upgrade those governors to contractGovernorExecutor.js, - * which will terminate the governed contracts. - * - * @param {BootstrapPowers} powers - * @param {{ options: { governorExecutorBundleId: string, targetSpecifiers: string[] } }} config - */ -export const upgradeGovernors = async ( - { consume: { board, governedContractKits } }, - { options: { governorExecutorBundleId, targetSpecifiers } }, -) => { - const { Fail, quote: q } = assert; - assert.typeof(governorExecutorBundleId, 'string'); - const targets = parseTargets(targetSpecifiers, Fail); - const doneP = Promise.allSettled( - targets.map(async ({ boardID, instanceKitLabel }) => { - const logLabel = [boardID, instanceKitLabel]; - const contractInstanceHandle = await E(board).getValue(boardID); - const instanceKit = await E(governedContractKits).get( - // @ts-expect-error TS2345 Property '[tag]' is missing - contractInstanceHandle, - ); - console.log( - `${q(logLabel)} alleged governor contract instance kit`, - instanceKit, - ); - const { label, governorAdminFacet, adminFacet } = instanceKit; - label === instanceKitLabel || - Fail`${q(logLabel)} unexpected instanceKit label, got ${label} but wanted ${q(instanceKitLabel)}`; - (adminFacet && adminFacet !== governorAdminFacet) || - Fail`${q(logLabel)} instanceKit adminFacet should have been present and different from governorAdminFacet but was ${adminFacet}`; - await E(governorAdminFacet).upgradeContract( - governorExecutorBundleId, - undefined, - ); - console.log(`${q(logLabel)} terminated governor`); - console.log('shutting down executor governor'); - const reason = harden(Error(`core-eval terminating ${label} governor`)); - await E(governorAdminFacet).terminateContract(reason); - }), - ); - const results = await doneP; - const problems = targets.flatMap(({ boardID, instanceKitLabel }, i) => { - if (results[i].status === 'fulfilled') return []; - return [[boardID, instanceKitLabel, results[i].reason]]; - }); - if (problems.length !== 0) { - console.error('governor termination(s) failed', problems); - Fail`governor termination(s) failed: ${problems}`; - } -}; -harden(upgradeGovernors); - -/* -export const getManifest = (_powers, targetSpecifiers) => { - parseTargets(targetSpecifiers); - return { - manifest: { - [upgradeGovernors.name]: { - consume: { board: true, governedContractKits: true }, - }, - }, - // Provide `terminateGovernors` a second argument like - // `{ options: { targetSpecifiers } }`. - options: { targetSpecifiers }, - }; -}; -*/ - -const uG = 'upgradeGovernors'; -/** - * Return the manifest, installations, and options for upgrading Vaults. - * - * @param {object} utils - * @param {any} utils.restoreRef - * @param {any} vaultUpgradeOptions - */ -export const getManifest = async ( - { restoreRef }, - { bundleRef, targetSpecifiers }, -) => { - return { - manifest: { - [upgradeGovernors.name]: { - consume: { - board: uG, - zoe: uG, - governedContractKits: uG, - }, - }, - }, - installations: { governorExecutor: restoreRef(bundleRef) }, // do we need installations ??? - options: { governorExecutorBundleId: bundleRef.bundleID, targetSpecifiers }, - }; -}; - -/* @type {import('@agoric/deploy-script-support/src/externalTypes.js').CoreEvalBuilder} */ -/* -export const defaultProposalBuilder = async (_utils, targetSpecifiers) => { - parseTargets(targetSpecifiers); - return harden({ - sourceSpec: SELF, - getManifestCall: ['getManifest', targetSpecifiers], - }); -};*/ - -/* @type {import('@agoric/deploy-script-support/src/externalTypes.js').DeployScriptFunction} */ -/* -export default async (homeP, endowments) => { - const { scriptArgs } = endowments; - parseTargets(scriptArgs, makeUsageError); - const dspModule = await import('@agoric/deploy-script-support'); - const { makeHelpers } = dspModule; - const { writeCoreEval } = await makeHelpers(homeP, endowments); - await writeCoreEval(upgradeGovernors.name, utils => - defaultProposalBuilder(utils, scriptArgs), - ); -}; -*/ -// import { makeHelpers } from '@agoric/deploy-script-support'; - -/** @type {import('@agoric/deploy-script-support/src/externalTypes.js').CoreEvalBuilder} */ -export const defaultProposalBuilder = async ( - { publishRef, install }, - targetSpecifiers, -) => { - parseTargets(targetSpecifiers); - return harden({ - sourceSpec: SELF, - getManifestCall: [ - getManifest.name, - { - bundleRef: publishRef( - install('@agoric/governance/src/contractGovernorExecutor.js'), - ), - targetSpecifiers, - }, - ], - }); -}; - -/* @type {import('@agoric/deploy-script-support/src/externalTypes.js').DeployScriptFunction} */ -/* -export default async (homeP, endowments) => { - const { writeCoreEval } = await makeHelpers(homeP, endowments); - await writeCoreEval('upgrade-governor-instance', defaultProposalBuilder); -}; -*/ - -/** @type {import('@agoric/deploy-script-support/src/externalTypes.js').DeployScriptFunction} */ -export default async (homeP, endowments) => { - const { scriptArgs } = endowments; - parseTargets(scriptArgs, makeUsageError); - const dspModule = await import('@agoric/deploy-script-support'); - const { makeHelpers } = dspModule; - const { writeCoreEval } = await makeHelpers(homeP, endowments); - await writeCoreEval(upgradeGovernors.name, utils => - defaultProposalBuilder(utils, scriptArgs), - ); -}; diff --git a/packages/governance/src/contractGovernorExecutor.js b/packages/governance/src/contractGovernorExecutor.js deleted file mode 100644 index 733604d4dc1..00000000000 --- a/packages/governance/src/contractGovernorExecutor.js +++ /dev/null @@ -1,60 +0,0 @@ -import { E } from '@endo/eventual-send'; -import { makeTracer } from '@agoric/internal'; -import { prepareExoClassKit } from '@agoric/vat-data'; - -const trace = makeTracer('CGExec', false); - -/** @type {ContractMeta} */ -export const meta = { - upgradability: 'canUpgrade', -}; -harden(meta); - -/** - * Start an instance of a governor, governing a "governed" contract specified in terms. - * - * @param {ZCF<{}>} zcf - * @param {{}} _privateArgs - * @param {import('@agoric/vat-data').Baggage} baggage - */ -export const start = async (zcf, _privateArgs, baggage) => { - trace('start'); - const makeContractGovernorKit = prepareExoClassKit( - baggage, - 'ContractGovernorKit', - undefined, - () => { - /** @type {Awaited>} */ - // @ts-expect-error - const kit = null; - return kit; - }, - { - creator: { - terminateInstance() { - const { adminFacet: contractInstanceAdminFacet } = this.state; - const terminationData = harden( - Error(`termination of contract by executor governor`), - ); - // we can't await remote calls in our 1st crank - // so fire-and-forget - void E(contractInstanceAdminFacet) - .terminateContract(terminationData) - .catch(err => { - console.log('FYI:', err); - }); - console.log('initiated termination of instance'); // TODO: what instance? - }, - }, - helper: {}, - public: {}, - }, - ); - console.log('defined ContractGovernorKit kind', makeContractGovernorKit); - /** @type {ReturnType} */ - const governorKit = baggage.get('contractGovernorKit'); - await governorKit.creator.terminateInstance(); - - return { creatorFacet: governorKit.creator, publicFacet: governorKit.public }; -}; -harden(start); From ad07e7b9194383c2267f6a514729a4e40a069fbf Mon Sep 17 00:00:00 2001 From: Siarhei Liakh Date: Wed, 22 Jan 2025 11:31:01 -0500 Subject: [PATCH 7/9] feat(cosmos/app): add terminateAuctioneersCoreProposal to upgrade18Handler refs: #10725 --- golang/cosmos/app/upgrade.go | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/golang/cosmos/app/upgrade.go b/golang/cosmos/app/upgrade.go index 657d76ce85e..f842acb4a13 100644 --- a/golang/cosmos/app/upgrade.go +++ b/golang/cosmos/app/upgrade.go @@ -192,20 +192,21 @@ func terminateGovernorCoreProposal(upgradeName string) (vm.CoreProposalStep, err ) } -func upgradeGovernorExecutorCoreProposal(upgradeName string) (vm.CoreProposalStep, error) { +func terminateAuctioneersCoreProposal(upgradeName string) (vm.CoreProposalStep, error) { // targets is a slice of "$boardID:$instanceKitLabel" strings. var targets []string switch getVariantFromUpgradeName(upgradeName) { case "EMERYNET": - targets = []string{"board04149:auctioneer"} // v38: governor for v39 - //"boardXXXYYY:autioneer", - // fixme: need some targets here + targets = []string{ + "board04149:auctioneer", // auctioneer v39, governor v38 + "board0501277:auctioneer", // auctioneer v665, governor v664 + } default: return nil, nil } return buildProposalStepWithArgs( - "@agoric/builders/scripts/vats/upgrade-governor-instance.js", + "@agoric/builders/scripts/vats/terminate-governed-instance.js", // Request `defaultProposalBuilder(powers, targets)`. "defaultProposalBuilder", []any{targets}, @@ -333,6 +334,13 @@ func upgrade18Handler(app *GaiaApp, targetUpgrade string) func(sdk.Context, upgr } else if terminateOldGovernor != nil { CoreProposalSteps = append(CoreProposalSteps, terminateOldGovernor) } + + terminateAuctioneers, err := terminateAuctioneersCoreProposal(targetUpgrade) + if err != nil { + return nil, err + } else if terminateAuctioneers != nil { + CoreProposalSteps = append(CoreProposalSteps, terminateAuctioneers) + } } app.upgradeDetails = &upgradeDetails{ From 09d80710fa06a31d19e6c36cbca291a1b889aa54 Mon Sep 17 00:00:00 2001 From: Siarhei Liakh Date: Wed, 22 Jan 2025 12:09:10 -0500 Subject: [PATCH 8/9] chore: lint-fix refs: #10725 --- .../bootstrapTests/terminate-governed.test.ts | 87 +++++++++---------- .../contractGovernor/add-governedContract.js | 56 ++++++------ .../contractGovernor/governedContract.js | 6 +- packages/internal/src/module-utils.js | 23 ++--- 4 files changed, 83 insertions(+), 89 deletions(-) diff --git a/packages/boot/test/bootstrapTests/terminate-governed.test.ts b/packages/boot/test/bootstrapTests/terminate-governed.test.ts index dbfc7df67c6..3a023de31ea 100644 --- a/packages/boot/test/bootstrapTests/terminate-governed.test.ts +++ b/packages/boot/test/bootstrapTests/terminate-governed.test.ts @@ -6,69 +6,64 @@ import { makeSwingsetTestKit } from '../../tools/supports.js'; const PLATFORM_CONFIG = '@agoric/vm-config/decentral-main-vaults-config.json'; const makeDefaultTestContext = async t => { - const swingsetTestKit = await makeSwingsetTestKit(t.log, undefined, { - configSpecifier: PLATFORM_CONFIG, - }); - const { runUtils } = swingsetTestKit; - const { EV } = runUtils; + const swingsetTestKit = await makeSwingsetTestKit(t.log, undefined, { + configSpecifier: PLATFORM_CONFIG, + }); + const { runUtils } = swingsetTestKit; + const { EV } = runUtils; - // We need to poke at bootstrap vat and wait for results to allow - // SwingSet to finish its boot process before we start the test - const zoe: ZoeService = await EV.vat('bootstrap').consumeItem('zoe'); + // We need to poke at bootstrap vat and wait for results to allow + // SwingSet to finish its boot process before we start the test + const zoe: ZoeService = await EV.vat('bootstrap').consumeItem('zoe'); - return { ...swingsetTestKit }; + return { ...swingsetTestKit }; }; const test = anyTest as TestFn< - Awaited> + Awaited> >; test.before(async t => { - t.context = await makeDefaultTestContext(t); + t.context = await makeDefaultTestContext(t); }); test.after.always(t => { - return t.context.shutdown && t.context.shutdown(); + return t.context.shutdown && t.context.shutdown(); }); test(`Create a contract via core-eval and kill it via core-eval by boardID `, async t => { + const TEST_CONTRACT_LABEL = 'testContractLabel'; + const { runUtils, buildProposal, evalProposal } = t.context; + const { EV } = runUtils; - const TEST_CONTRACT_LABEL = 'testContractLabel'; - const { runUtils, buildProposal, evalProposal } = t.context; - const { EV } = runUtils; + // create a contract via core-eval + const testContractProposalArgs = [TEST_CONTRACT_LABEL]; + const creatorProposalMaterials = buildProposal( + '@agoric/governance/test/swingsetTests/contractGovernor/add-governedContract.js', + testContractProposalArgs, + ); + await evalProposal(creatorProposalMaterials); - // create a contract via core-eval - const testContractProposalArgs = [TEST_CONTRACT_LABEL,]; - const creatorProposalMaterials = buildProposal( - '@agoric/governance/test/swingsetTests/contractGovernor/add-governedContract.js', - testContractProposalArgs, - ); - await evalProposal(creatorProposalMaterials); + const { boardID, publicFacet } = + await EV.vat('bootstrap').consumeItem(TEST_CONTRACT_LABEL); + console.log({ boardID }); - const { boardID, publicFacet } = await EV.vat('bootstrap').consumeItem(TEST_CONTRACT_LABEL); - console.log({ boardID }); + // confirming the contract actually works + const num = await EV(publicFacet).getNum(); + t.is(num, 602214090000000000000000n); - // confirming the contract actually works - const num = await EV(publicFacet).getNum(); - t.is(num, 602214090000000000000000n); + // killing via terminate-governed-instance + const targets = [`${boardID}:${TEST_CONTRACT_LABEL}`]; + console.log({ targets }); - // killing via terminate-governed-instance - const targets = [ - `${boardID}:${TEST_CONTRACT_LABEL}`, - ]; - console.log({ targets }); + const terminatorProposalMaterials = buildProposal( + '@agoric/builders/scripts/vats/terminate-governed-instance.js', + targets, + ); + await evalProposal(terminatorProposalMaterials); - const terminatorProposalMaterials = buildProposal( - '@agoric/builders/scripts/vats/terminate-governed-instance.js', - targets, - ); - await evalProposal(terminatorProposalMaterials); - - // confirm the contract is no longer there - await t.throwsAsync( - () => EV(publicFacet).getNum(), - { - message: 'vat terminated', - } - ); -}); \ No newline at end of file + // confirm the contract is no longer there + await t.throwsAsync(() => EV(publicFacet).getNum(), { + message: 'vat terminated', + }); +}); diff --git a/packages/governance/test/swingsetTests/contractGovernor/add-governedContract.js b/packages/governance/test/swingsetTests/contractGovernor/add-governedContract.js index 04ceea1eb92..621d4c241ee 100644 --- a/packages/governance/test/swingsetTests/contractGovernor/add-governedContract.js +++ b/packages/governance/test/swingsetTests/contractGovernor/add-governedContract.js @@ -40,25 +40,18 @@ export const startGovernedInstance = async ( produce, }, { - options: { - contractInstallation, - producedKey, - label = 'governedContract', - }, + options: { contractInstallation, producedKey, label = 'governedContract' }, }, ) => { trace('Governed Contract start'); const poserInvitationP = E(electorateCreatorFacet).getPoserInvitation(); - const [ - chainTimerService, - poserInvitation, - governedContractKits, - ] = await Promise.all([ - chainTimerServiceP, - poserInvitationP, - governedContractKitsP, - ]); + const [chainTimerService, poserInvitation, governedContractKits] = + await Promise.all([ + chainTimerServiceP, + poserInvitationP, + governedContractKitsP, + ]); const invitationIssuer = await E(zoe).getInvitationIssuer(); const invitationAmount = @@ -97,7 +90,7 @@ export const startGovernedInstance = async ( {}, governorTerms, { - //electorateCreatorFacet, + // electorateCreatorFacet, governed: { initialPoserInvitation: poserInvitation, }, @@ -105,17 +98,19 @@ export const startGovernedInstance = async ( `${label}.governor`, ); - const kit = await deeplyFulfilledObject(harden({ - label, - creatorFacet: E(governorStartResult.creatorFacet).getCreatorFacet(), - adminFacet: E(governorStartResult.creatorFacet).getAdminFacet(), - publicFacet: E(governorStartResult.creatorFacet).getPublicFacet(), - instance: E(governorStartResult.creatorFacet).getInstance(), - - governor: governorStartResult.instance, - governorCreatorFacet: governorStartResult.creatorFacet, - governorAdminFacet: governorStartResult.adminFacet, - })); + const kit = await deeplyFulfilledObject( + harden({ + label, + creatorFacet: E(governorStartResult.creatorFacet).getCreatorFacet(), + adminFacet: E(governorStartResult.creatorFacet).getAdminFacet(), + publicFacet: E(governorStartResult.creatorFacet).getPublicFacet(), + instance: E(governorStartResult.creatorFacet).getInstance(), + + governor: governorStartResult.instance, + governorCreatorFacet: governorStartResult.creatorFacet, + governorAdminFacet: governorStartResult.adminFacet, + }), + ); governedContractKits.init(kit.instance, kit); @@ -160,7 +155,10 @@ export const getManifestForGovernedContract = async ( }; /** @type {import('@agoric/deploy-script-support/src/externalTypes.js').CoreEvalBuilder} */ -export const defaultProposalBuilder = async ({ publishRef, install }, [producedKey, label = producedKey] = []) => { +export const defaultProposalBuilder = async ( + { publishRef, install }, + [producedKey, label = producedKey] = [], +) => { const { getSpecifier } = await import('@agoric/internal/src/module-utils.js'); const SELF = await getSpecifier(import.meta.url); @@ -175,9 +173,7 @@ export const defaultProposalBuilder = async ({ publishRef, install }, [producedK ), ), governorBundleRef: publishRef( - install( - '@agoric/governance/src/contractGovernor.js', - ), + install('@agoric/governance/src/contractGovernor.js'), ), producedKey, label, diff --git a/packages/governance/test/swingsetTests/contractGovernor/governedContract.js b/packages/governance/test/swingsetTests/contractGovernor/governedContract.js index e9c2cc4e5aa..06c7b7bdbcb 100644 --- a/packages/governance/test/swingsetTests/contractGovernor/governedContract.js +++ b/packages/governance/test/swingsetTests/contractGovernor/governedContract.js @@ -1,7 +1,7 @@ +import { Far } from '@endo/marshal'; import { handleParamGovernance } from '../../../src/contractHelper.js'; import { ParamTypes } from '../../../src/index.js'; import { CONTRACT_ELECTORATE } from '../../../src/contractGovernance/governParam.js'; -import { Far } from '@endo/marshal'; /** * @import {GovernanceTerms} from '../../../src/types.js'; @@ -42,7 +42,9 @@ const start = async (zcf, privateArgs) => { getNum: () => params.getMalleableNumber(), getApiCalled: () => governanceAPICalled, }), - creatorFacet: makeFarGovernorFacet(Far('governedContract creatorFacet'), { governanceApi }), + creatorFacet: makeFarGovernorFacet(Far('governedContract creatorFacet'), { + governanceApi, + }), }; }; diff --git a/packages/internal/src/module-utils.js b/packages/internal/src/module-utils.js index 5da942715d7..0918055ac97 100644 --- a/packages/internal/src/module-utils.js +++ b/packages/internal/src/module-utils.js @@ -4,20 +4,21 @@ import { search } from '@endo/compartment-mapper'; import { Fail } from '@endo/errors'; /** - * Get a deep-import specifier for a file, starting with the specifier for its containing module. + * Get a deep-import specifier for a file, starting with the specifier for its + * containing module. * * @param {string} fileUrl * @returns {Promise} */ export const getSpecifier = async fileUrl => { - const read = async location => { - const path = fileURLToPath(new URL(location, fileUrl)); - return readFile(path); - }; - const searchResult = await search(read, fileUrl); - const { packageDescriptorText, moduleSpecifier } = searchResult; - const { name } = JSON.parse(packageDescriptorText); - name || Fail`file package has no name`; - return `${name}/${moduleSpecifier}`; + const read = async location => { + const path = fileURLToPath(new URL(location, fileUrl)); + return readFile(path); + }; + const searchResult = await search(read, fileUrl); + const { packageDescriptorText, moduleSpecifier } = searchResult; + const { name } = JSON.parse(packageDescriptorText); + name || Fail`file package has no name`; + return `${name}/${moduleSpecifier}`; }; -harden(getSpecifier); \ No newline at end of file +harden(getSpecifier); From 196459747586adee4a62dfbed17193e7604ae352 Mon Sep 17 00:00:00 2001 From: siarhei-agoric Date: Thu, 23 Jan 2025 13:50:10 -0500 Subject: [PATCH 9/9] fixup: Apply suggestions from code review Co-authored-by: Richard Gibson --- packages/governance/package.json | 3 +- .../contractGovernor/add-governedContract.js | 93 +++++++++++-------- packages/internal/src/module-utils.js | 10 +- 3 files changed, 60 insertions(+), 46 deletions(-) diff --git a/packages/governance/package.json b/packages/governance/package.json index c794f4c1455..06af6074bd2 100644 --- a/packages/governance/package.json +++ b/packages/governance/package.json @@ -53,8 +53,7 @@ "@endo/bundle-source": "^3.5.0", "@endo/init": "^1.1.7", "ava": "^5.3.0", - "c8": "^10.1.2", - "@endo/compartment-mapper": "^1.4.0" + "c8": "^10.1.2" }, "files": [ "README.md", diff --git a/packages/governance/test/swingsetTests/contractGovernor/add-governedContract.js b/packages/governance/test/swingsetTests/contractGovernor/add-governedContract.js index 621d4c241ee..a7c210076bc 100644 --- a/packages/governance/test/swingsetTests/contractGovernor/add-governedContract.js +++ b/packages/governance/test/swingsetTests/contractGovernor/add-governedContract.js @@ -1,4 +1,14 @@ -// import { makeHelpers } from '@agoric/deploy-script-support'; +/** + * @file Source for a core-eval to start a new governed instance of + * ./governedContract.js. + * Also functions as an off-chain builder script for such core-evals: + * agoric run /path/to/$0 [ [