diff --git a/packages/agoric-cli/src/init.js b/packages/agoric-cli/src/init.js index a81283f8487..b901128b383 100644 --- a/packages/agoric-cli/src/init.js +++ b/packages/agoric-cli/src/init.js @@ -40,9 +40,10 @@ export default async function initMain(_progname, rawArgs, priv, opts) { dappBranch = ['-b', opts.dappBranch]; } + const shallow = ['--depth', '1', '--shallow-submodules']; const exitStatus = await pspawn( 'git', - ['clone', '--origin=upstream', dappURL, DIR, ...dappBranch], + ['clone', '--origin=upstream', ...shallow, dappURL, DIR, ...dappBranch], { stdio: 'inherit', }, diff --git a/packages/agoric-cli/test/upgrade-contract/propose-buggy-contract.js b/packages/agoric-cli/test/upgrade-contract/propose-buggy-contract.js index 1e5829e6511..1420d080f7e 100644 --- a/packages/agoric-cli/test/upgrade-contract/propose-buggy-contract.js +++ b/packages/agoric-cli/test/upgrade-contract/propose-buggy-contract.js @@ -3,6 +3,7 @@ // eslint-disable-next-line import/no-extraneous-dependencies import { makeHelpers } from '@agoric/deploy-script-support'; +/** @type {import('@agoric/deploy-script-support/src/externalTypes.js').ProposalBuilder} */ export const defaultProposalBuilder = async ({ publishRef, install }) => harden({ sourceSpec: './init-proposal.js', diff --git a/packages/agoric-cli/test/upgrade-contract/propose-upgrade-contract.js b/packages/agoric-cli/test/upgrade-contract/propose-upgrade-contract.js index b149f7ef75b..60ad54bb46c 100644 --- a/packages/agoric-cli/test/upgrade-contract/propose-upgrade-contract.js +++ b/packages/agoric-cli/test/upgrade-contract/propose-upgrade-contract.js @@ -3,6 +3,7 @@ // eslint-disable-next-line import/no-extraneous-dependencies import { makeHelpers } from '@agoric/deploy-script-support'; +/** @type {import('@agoric/deploy-script-support/src/externalTypes.js').ProposalBuilder} */ export const defaultProposalBuilder = async ({ publishRef, install }) => harden({ sourceSpec: './upgrade-proposal.js', diff --git a/packages/cosmic-swingset/package.json b/packages/cosmic-swingset/package.json index 06f331abe86..0b01815be77 100644 --- a/packages/cosmic-swingset/package.json +++ b/packages/cosmic-swingset/package.json @@ -30,6 +30,7 @@ "@agoric/swing-store": "^0.9.2-u13.0", "@agoric/swingset-vat": "^0.32.3-u13.0", "@agoric/telemetry": "^0.6.3-u13.0", + "@endo/bundle-source": "2.5.2-upstream-rollup", "@endo/far": "0.2.18", "@endo/import-bundle": "0.3.4", "@endo/init": "0.5.56", diff --git a/packages/cosmic-swingset/src/chain-main.js b/packages/cosmic-swingset/src/chain-main.js index 287b23a324d..305079d7118 100644 --- a/packages/cosmic-swingset/src/chain-main.js +++ b/packages/cosmic-swingset/src/chain-main.js @@ -359,12 +359,15 @@ export default async function main(progname, args, { env, homedir, agcc }) { const argv = { bootMsg, }; - const vatHref = await importMetaResolve( - env.CHAIN_BOOTSTRAP_VAT_CONFIG || - argv.bootMsg.params.bootstrap_vat_config, - import.meta.url, - ); - const vatconfig = new URL(vatHref).pathname; + const getVatConfig = async () => { + const vatHref = await importMetaResolve( + env.CHAIN_BOOTSTRAP_VAT_CONFIG || + argv.bootMsg.params.bootstrap_vat_config, + import.meta.url, + ); + const vatconfig = new URL(vatHref).pathname; + return vatconfig; + }; // Delay makeShutdown to override the golang interrupts const { registerShutdown } = makeShutdown(); @@ -463,7 +466,7 @@ export default async function main(progname, args, { env, homedir, agcc }) { clearChainSends, replayChainSends, bridgeOutbound: doOutboundBridge, - vatconfig, + vatconfig: getVatConfig, argv, env, verboseBlocks: true, diff --git a/packages/cosmic-swingset/src/helpers/json.js b/packages/cosmic-swingset/src/helpers/json.js new file mode 100644 index 00000000000..af6b991c171 --- /dev/null +++ b/packages/cosmic-swingset/src/helpers/json.js @@ -0,0 +1,19 @@ +// @ts-check + +/** + * Parses JSON and, if necessary, throws exceptions that include the location + * of the offending file. + * + * @param {string} source + * @param {string} location + */ +export const parseLocatedJson = (source, location) => { + try { + return JSON.parse(source); + } catch (error) { + if (error instanceof SyntaxError) { + throw SyntaxError(`Cannot parse JSON from ${location}, ${error}`); + } + throw error; + } +}; diff --git a/packages/cosmic-swingset/src/launch-chain.js b/packages/cosmic-swingset/src/launch-chain.js index bc10c58c7cf..ec695710aa9 100644 --- a/packages/cosmic-swingset/src/launch-chain.js +++ b/packages/cosmic-swingset/src/launch-chain.js @@ -4,6 +4,8 @@ import anylogger from 'anylogger'; import { E } from '@endo/far'; +import bundleSource from '@endo/bundle-source'; + import { buildMailbox, buildMailboxStateMap, @@ -23,6 +25,7 @@ import { makeWithQueue } from '@agoric/internal/src/queue.js'; import * as ActionType from '@agoric/internal/src/action-types.js'; import { extractCoreProposalBundles } from '@agoric/deploy-script-support/src/extract-proposal.js'; +import { fileURLToPath } from 'url'; import { makeDefaultMeterProvider, @@ -37,8 +40,9 @@ import { BeansPerXsnapComputron, } from './sim-params.js'; import { parseParams } from './params.js'; -import { makeQueue } from './helpers/make-queue.js'; +import { makeQueue, makeQueueStorageMock } from './helpers/make-queue.js'; import { exportStorage } from './export-storage.js'; +import { parseLocatedJson } from './helpers/json.js'; const console = anylogger('launch-chain'); const blockManagerConsole = anylogger('block-manager'); @@ -68,7 +72,7 @@ const getHostKey = path => `host.${path}`; * @param {Map<*, *>} mailboxStorage * @param {undefined | ((dstID: string, obj: any) => any)} bridgeOutbound * @param {SwingStoreKernelStorage} kernelStorage - * @param {string} vatconfig absolute path + * @param {string | (() => string | Promise)} vatconfig absolute path or thunk * @param {unknown} bootstrapArgs JSON-serializable data * @param {{}} env * @param {*} options @@ -83,42 +87,48 @@ export async function buildSwingset( { debugName = undefined, slogCallbacks, slogSender }, ) { const debugPrefix = debugName === undefined ? '' : `${debugName}:`; - let config = await loadSwingsetConfigFile(vatconfig); - if (config === null) { - config = loadBasedir(vatconfig); - } - const mbs = buildMailboxStateMap(mailboxStorage); - const timer = buildTimer(); - const mb = buildMailbox(mbs); - config.devices = { - mailbox: { - sourceSpec: mb.srcPath, - }, - timer: { - sourceSpec: timer.srcPath, - }, - }; + + const bridgeDevice = bridgeOutbound && buildBridge(bridgeOutbound); + const mailboxDevice = buildMailbox(mbs); + const timerDevice = buildTimer(); + const deviceEndowments = { - mailbox: { ...mb.endowments }, - timer: { ...timer.endowments }, + mailbox: { ...mailboxDevice.endowments }, + timer: { ...timerDevice.endowments }, }; - - let bridgeInbound; - if (bridgeOutbound) { - const bd = buildBridge(bridgeOutbound); - config.devices.bridge = { - sourceSpec: bd.srcPath, - }; - deviceEndowments.bridge = { ...bd.endowments }; - bridgeInbound = bd.deliverInbound; + if (bridgeDevice) { + deviceEndowments.bridge = { ...bridgeDevice.endowments }; } async function ensureSwingsetInitialized() { if (swingsetIsInitialized(kernelStorage)) { return; } - if (!config) throw Fail`config not yet set`; + + const configLocation = await (typeof vatconfig === 'function' + ? vatconfig() + : vatconfig); + let config = await loadSwingsetConfigFile(configLocation); + if (config === null) { + config = loadBasedir(configLocation); + } + + config.devices = { + mailbox: { + sourceSpec: mailboxDevice.srcPath, + }, + timer: { + sourceSpec: timerDevice.srcPath, + }, + }; + + if (bridgeDevice) { + config.devices.bridge = { + sourceSpec: bridgeDevice.srcPath, + }; + } + const { coreProposals, clearStorageSubtrees, @@ -135,7 +145,7 @@ export async function buildSwingset( if (coreProposals) { const { bundles, code } = await extractCoreProposalBundles( coreProposals, - vatconfig, // for path resolution + configLocation, // for path resolution ); swingsetConfig.bundles = { ...swingsetConfig.bundles, ...bundles }; @@ -162,6 +172,7 @@ export async function buildSwingset( debugPrefix, }); } + await ensureSwingsetInitialized(); const controller = await makeSwingsetController( kernelStorage, @@ -176,13 +187,19 @@ export async function buildSwingset( // We DON'T want to run the kernel yet, only when the application decides // (either on bootstrap block (0) or in endBlock). - return { controller, mb, bridgeInbound, timer }; + return { + controller, + mb: mailboxDevice, + bridgeInbound: bridgeDevice && bridgeDevice.deliverInbound, + timer: timerDevice, + }; } /** * @typedef {import('@agoric/swingset-vat').RunPolicy & { * shouldRun(): boolean; - * remainingBeans(): bigint; + * remainingBeans(): bigint | undefined; + * totalBeans(): bigint; * }} ChainRunPolicy */ @@ -195,19 +212,24 @@ export async function buildSwingset( /** * @param {BeansPerUnit} beansPerUnit + * @param {boolean} [ignoreBlockLimit] * @returns {ChainRunPolicy} */ -function computronCounter({ - [BeansPerBlockComputeLimit]: blockComputeLimit, - [BeansPerVatCreation]: vatCreation, - [BeansPerXsnapComputron]: xsnapComputron, -}) { +function computronCounter( + { + [BeansPerBlockComputeLimit]: blockComputeLimit, + [BeansPerVatCreation]: vatCreation, + [BeansPerXsnapComputron]: xsnapComputron, + }, + ignoreBlockLimit = false, +) { assert.typeof(blockComputeLimit, 'bigint'); assert.typeof(vatCreation, 'bigint'); assert.typeof(xsnapComputron, 'bigint'); let totalBeans = 0n; - const shouldRun = () => totalBeans < blockComputeLimit; - const remainingBeans = () => blockComputeLimit - totalBeans; + const shouldRun = () => ignoreBlockLimit || totalBeans < blockComputeLimit; + const remainingBeans = () => + ignoreBlockLimit ? undefined : blockComputeLimit - totalBeans; const policy = harden({ vatCreated() { @@ -231,23 +253,17 @@ function computronCounter({ return shouldRun(); }, emptyCrank() { - return true; + return shouldRun(); }, shouldRun, remainingBeans, + totalBeans() { + return totalBeans; + }, }); return policy; } -function neverStop() { - return harden({ - vatCreated: () => true, - crankComplete: () => true, - crankFailed: () => true, - emptyCrank: () => true, - }); -} - export async function launch({ actionQueueStorage, highPriorityQueueStorage, @@ -296,6 +312,14 @@ export async function launch({ const actionQueue = makeQueue(actionQueueStorage); /** @type {InboundQueue} */ const highPriorityQueue = makeQueue(highPriorityQueueStorage); + /** + * In memory queue holding actions that must be consumed entirely + * during the block. If it's not drained, we open the gates to + * hangover hell. + * + * @type {InboundQueue} + */ + const runThisBlock = makeQueue(makeQueueStorageMock().storage); // Not to be confused with the gas model, this meter is for OpenTelemetry. const metricMeter = metricsProvider.getMeter('ag-chain-cosmos'); @@ -337,14 +361,55 @@ export async function launch({ inboundQueueMetrics, }); - async function bootstrapBlock(_blockHeight, blockTime) { + /** + * @param {number} blockHeight + * @param {ChainRunPolicy} runPolicy + */ + function makeRunSwingset(blockHeight, runPolicy) { + let runNum = 0; + async function runSwingset() { + const startBeans = runPolicy.totalBeans(); + controller.writeSlogObject({ + type: 'cosmic-swingset-run-start', + blockHeight, + runNum, + startBeans, + remainingBeans: runPolicy.remainingBeans(), + }); + // TODO: crankScheduler does a schedulerBlockTimeHistogram thing + // that needs to be revisited, it used to be called once per + // block, now it's once per processed inbound queue item + await crankScheduler(runPolicy); + const finishBeans = runPolicy.totalBeans(); + controller.writeSlogObject({ + type: 'kernel-stats', + stats: controller.getStats(), + }); + controller.writeSlogObject({ + type: 'cosmic-swingset-run-finish', + blockHeight, + runNum, + startBeans, + finishBeans, + usedBeans: finishBeans - startBeans, + remainingBeans: runPolicy.remainingBeans(), + }); + runNum += 1; + return runPolicy.shouldRun(); + } + return runSwingset; + } + + async function bootstrapBlock(blockHeight, blockTime, params) { // We need to let bootstrap know of the chain time. The time of the first // block may be the genesis time, or the block time of the upgrade block. timer.poll(blockTime); // This is before the initial block, we need to finish processing the // entire bootstrap before opening for business. - const policy = neverStop(); - await crankScheduler(policy); + const runPolicy = computronCounter(params.beansPerUnit, true); + const runSwingset = makeRunSwingset(blockHeight, runPolicy); + + await runSwingset(); } async function saveChainState() { @@ -387,10 +452,10 @@ export async function launch({ bridgeInbound(source, body); } - async function installBundle(bundleSource) { + async function installBundle(bundleJson) { let bundle; try { - bundle = JSON.parse(bundleSource); + bundle = JSON.parse(bundleJson); } catch (e) { blockManagerConsole.warn('INSTALL_BUNDLE warn:', e); return; @@ -494,67 +559,46 @@ export async function launch({ return p; } - async function runKernel(runPolicy, blockHeight, blockTime) { - let runNum = 0; - async function runSwingset() { - const initialBeans = runPolicy.remainingBeans(); - controller.writeSlogObject({ - type: 'cosmic-swingset-run-start', - blockHeight, - runNum, - initialBeans, - }); - // TODO: crankScheduler does a schedulerBlockTimeHistogram thing - // that needs to be revisited, it used to be called once per - // block, now it's once per processed inbound queue item - await crankScheduler(runPolicy); - const remainingBeans = runPolicy.remainingBeans(); - controller.writeSlogObject({ - type: 'kernel-stats', - stats: controller.getStats(), - }); - controller.writeSlogObject({ - type: 'cosmic-swingset-run-finish', - blockHeight, - runNum, - remainingBeans, - usedBeans: initialBeans - remainingBeans, - }); - runNum += 1; - return runPolicy.shouldRun(); - } - - /** - * Process as much as we can from an inbound queue, which contains - * first the old actions not previously processed, followed by actions - * newly added, running the kernel to completion after each. - * - * @param {InboundQueue} inboundQueue - */ - async function processActions(inboundQueue) { - let keepGoing = true; - for (const { action, context } of inboundQueue.consumeAll()) { - const inboundNum = `${context.blockHeight}-${context.txHash}-${context.msgIdx}`; - inboundQueueMetrics.decStat(); - // eslint-disable-next-line no-await-in-loop - await performAction(action, inboundNum); - // eslint-disable-next-line no-await-in-loop - keepGoing = await runSwingset(); - if (!keepGoing) { - // any leftover actions will remain on the inbound queue for possible - // processing in the next block - break; - } + /** + * Process as much as we can from an inbound queue, which contains + * first the old actions not previously processed, followed by actions + * newly added, running the kernel to completion after each. + * + * @param {InboundQueue} inboundQueue + * @param {ReturnType} runSwingset + */ + async function processActions(inboundQueue, runSwingset) { + let keepGoing = true; + for (const { action, context } of inboundQueue.consumeAll()) { + const inboundNum = `${context.blockHeight}-${context.txHash}-${context.msgIdx}`; + inboundQueueMetrics.decStat(); + // eslint-disable-next-line no-await-in-loop + await performAction(action, inboundNum); + // eslint-disable-next-line no-await-in-loop + keepGoing = await runSwingset(); + if (!keepGoing) { + // any leftover actions will remain on the inbound queue for possible + // processing in the next block + break; } - return keepGoing; } + return keepGoing; + } + async function runKernel(runSwingset, blockHeight, blockTime) { // First, complete leftover work, if any let keepGoing = await runSwingset(); if (!keepGoing) return; + // Then, if we have anything in the special runThisBlock queue, process + // it and do no further work. + if (runThisBlock.size()) { + await processActions(runThisBlock, runSwingset); + return; + } + // Then, process as much as we can from the priorityQueue. - keepGoing = await processActions(highPriorityQueue); + keepGoing = await processActions(highPriorityQueue, runSwingset); if (!keepGoing) return; // Then, update the timer device with the new external time, which might @@ -571,7 +615,7 @@ export async function launch({ if (!keepGoing) return; // Finally, process as much as we can from the actionQueue. - await processActions(actionQueue); + await processActions(actionQueue, runSwingset); } async function endBlock(blockHeight, blockTime, params) { @@ -582,13 +626,18 @@ export async function launch({ // First, record new actions (bridge/mailbox/etc events that cosmos // added up for delivery to swingset) into our inboundQueue metrics inboundQueueMetrics.updateLength( - actionQueue.size() + highPriorityQueue.size(), + actionQueue.size() + highPriorityQueue.size() + runThisBlock.size(), ); + // If we have work to complete this block, it needs to run to completion. + // It will also run to completion any work that swingset still had pending. + const neverStop = runThisBlock.size() > 0; + // make a runPolicy that will be shared across all cycles - const runPolicy = computronCounter(params.beansPerUnit); + const runPolicy = computronCounter(params.beansPerUnit, neverStop); + const runSwingset = makeRunSwingset(blockHeight, runPolicy); - await runKernel(runPolicy, blockHeight, blockTime); + await runKernel(runSwingset, blockHeight, blockTime); if (END_BLOCK_SPIN_MS) { // Introduce a busy-wait to artificially put load on the chain. @@ -692,34 +741,24 @@ export async function launch({ // ); switch (action.type) { case ActionType.AG_COSMOS_INIT: { - const { isBootstrap, upgradePlan, blockTime } = action; + const { isBootstrap, upgradePlan, blockTime, params } = action; // This only runs for the very first block on the chain. if (isBootstrap) { verboseBlocks && blockManagerConsole.info('block bootstrap'); (savedHeight === 0 && savedBeginHeight === 0) || Fail`Cannot run a bootstrap block at height ${savedHeight}`; + const bootstrapBlockParams = parseParams(params); const blockHeight = 0; - const runNum = 0; controller.writeSlogObject({ type: 'cosmic-swingset-bootstrap-block-start', blockTime, }); - controller.writeSlogObject({ - type: 'cosmic-swingset-run-start', - blockHeight, - runNum, - }); // Start a block transaction, but without changing state // for the upcoming begin block check saveBeginHeight(savedBeginHeight); await processAction(action.type, async () => - bootstrapBlock(blockHeight, blockTime), + bootstrapBlock(blockHeight, blockTime, bootstrapBlockParams), ); - controller.writeSlogObject({ - type: 'cosmic-swingset-run-finish', - blockHeight, - runNum, - }); controller.writeSlogObject({ type: 'cosmic-swingset-bootstrap-block-finish', blockTime, @@ -727,24 +766,103 @@ export async function launch({ } if (upgradePlan) { const blockHeight = upgradePlan.height; - if (blockNeedsExecution(blockHeight)) { - controller.writeSlogObject({ - type: 'cosmic-swingset-upgrade-start', - blockHeight, - blockTime, - upgradePlan, - }); - // TODO: Process upgrade plan - controller.writeSlogObject({ - type: 'cosmic-swingset-upgrade-finish', - blockHeight, - blockTime, - }); - } + + // Process upgrade plan + const upgradedAction = { + type: ActionType.ENACTED_UPGRADE, + upgradePlan, + blockHeight, + blockTime, + }; + await doBlockingSend(upgradedAction); } return true; } + case ActionType.ENACTED_UPGRADE: { + // Install and execute new core proposals. + const { upgradePlan, blockHeight, blockTime } = action; + + if (!blockNeedsExecution(blockHeight)) { + return undefined; + } + + // Start a block transaction, but without changing state + // for the upcoming begin block check + saveBeginHeight(savedBeginHeight); + + controller.writeSlogObject({ + type: 'cosmic-swingset-upgrade-start', + blockHeight, + blockTime, + upgradePlan, + }); + + const { info: upgradeInfoJson = null } = upgradePlan || {}; + + const upgradePlanInfo = + upgradeInfoJson && + parseLocatedJson(upgradeInfoJson, 'ENACTED_UPGRADE upgradePlan.info'); + + // Handle the planned core proposals as just another action. + const { coreProposals = [] } = upgradePlanInfo || {}; + + if (!coreProposals.length) { + // Nothing to do. + return undefined; + } + + // Find scripts relative to our location. + const myFilename = fileURLToPath(import.meta.url); + const { bundles, code: coreEvalCode } = + await extractCoreProposalBundles(coreProposals, myFilename, { + handleToBundleSpec: async (handle, source, _sequence, _piece) => { + const bundle = await bundleSource(source); + const { endoZipBase64Sha512: hash } = bundle; + const bundleID = `b1-${hash}`; + handle.bundleID = bundleID; + harden(handle); + return harden([`${bundleID}: ${source}`, bundle]); + }, + }); + + for (const [meta, bundle] of Object.entries(bundles)) { + // eslint-disable-next-line no-await-in-loop + await controller + .validateAndInstallBundle(bundle) + .catch(e => Fail`Cannot validate and install ${meta}: ${e}`); + } + + // Now queue the code for evaluation. + const coreEvalAction = { + type: ActionType.CORE_EVAL, + blockHeight, + blockTime, + evals: [ + { + json_permits: 'true', + js_code: coreEvalCode, + }, + ], + }; + runThisBlock.push({ + context: { + blockHeight, + txHash: 'x/upgrade', + msgIdx: 0, + }, + action: coreEvalAction, + }); + + controller.writeSlogObject({ + type: 'cosmic-swingset-upgrade-finish', + blockHeight, + blockTime, + }); + + return undefined; + } + case ActionType.COMMIT_BLOCK: { const { blockHeight, blockTime } = action; verboseBlocks && @@ -755,6 +873,9 @@ export async function launch({ ); } + runThisBlock.size() === 0 || + Fail`We didn't process all "run this block" actions`; + controller.writeSlogObject({ type: 'cosmic-swingset-commit-block-start', blockHeight, diff --git a/packages/cosmic-swingset/src/sim-chain.js b/packages/cosmic-swingset/src/sim-chain.js index 685c4674b37..4faf71a6142 100644 --- a/packages/cosmic-swingset/src/sim-chain.js +++ b/packages/cosmic-swingset/src/sim-chain.js @@ -34,7 +34,9 @@ async function makeMapStorage(file) { const map = new Map(); map.commit = async () => { const obj = {}; - [...map.entries()].forEach(([k, v]) => (obj[k] = exportMailbox(v))); + for (const [k, v] of map.entries()) { + obj[k] = exportMailbox(v); + } const json = stringify(obj); await fs.promises.writeFile(file, json); }; @@ -43,8 +45,11 @@ async function makeMapStorage(file) { content = await fs.promises.readFile(file); return JSON.parse(content); })().then( - obj => - Object.entries(obj).forEach(([k, v]) => map.set(k, importMailbox(v))), + obj => { + for (const [k, v] of Object.entries(obj)) { + map.set(k, importMailbox(v)); + } + }, () => {}, ); @@ -70,12 +75,15 @@ export async function connectToFakeChain(basedir, GCI, delay, inbound) { }, }; - const url = await importMetaResolve( - process.env.CHAIN_BOOTSTRAP_VAT_CONFIG || - argv.bootMsg.params.bootstrap_vat_config, - import.meta.url, - ); - const vatconfig = new URL(url).pathname; + const getVatConfig = async () => { + const url = await importMetaResolve( + process.env.CHAIN_BOOTSTRAP_VAT_CONFIG || + argv.bootMsg.params.bootstrap_vat_config, + import.meta.url, + ); + const vatconfig = new URL(url).pathname; + return vatconfig; + }; const stateDBdir = path.join(basedir, `fake-chain-${GCI}-state`); function replayChainSends() { Fail`Replay not implemented`; @@ -108,7 +116,7 @@ export async function connectToFakeChain(basedir, GCI, delay, inbound) { mailboxStorage, clearChainSends, replayChainSends, - vatconfig, + vatconfig: getVatConfig, argv, debugName: GCI, metricsProvider, @@ -208,6 +216,7 @@ export async function connectToFakeChain(basedir, GCI, delay, inbound) { type: 'AG_COSMOS_INIT', blockTime: scaleBlockTime(Date.now()), isBootstrap: true, + params: DEFAULT_SIM_SWINGSET_PARAMS, }); blockHeight = initialHeight; }; diff --git a/packages/cosmic-swingset/test/scenario2.js b/packages/cosmic-swingset/test/scenario2.js index bc5656da90e..ce316044eda 100644 --- a/packages/cosmic-swingset/test/scenario2.js +++ b/packages/cosmic-swingset/test/scenario2.js @@ -6,9 +6,8 @@ const onlyStderr = ['ignore', 'ignore', 'inherit']; const noOutput = ['ignore', 'ignore', 'ignore']; // const noisyDebug = ['ignore', 'inherit', 'inherit']; -export const pspawn = - (bin, { spawn, cwd }) => - (args = [], opts = {}) => { +export const pspawn = (bin, { spawn, cwd }) => { + return (args = [], opts = {}) => { /** @type {import('child_process').ChildProcess} */ let child; const exit = new Promise((resolve, reject) => { @@ -16,7 +15,9 @@ export const pspawn = child = spawn(bin, args, { cwd, ...opts }); child.addListener('exit', code => { if (code !== 0) { - reject(Error(`exit ${code} from: ${bin} ${args}`)); + // TODO: Include ~3 lines from child.stderr or child.stdout if present. + // see https://nodejs.org/api/child_process.html#child_processspawncommand-args-options + reject(Error(`exit ${code} from command: ${bin} ${args}`)); return; } resolve(0); @@ -35,6 +36,7 @@ export const pspawn = // @ts-expect-error child is set in the Promise constructor return { kill, child, exit }; }; +}; /** * Shared state for tests using scenario2 chain in ../ diff --git a/packages/deploy-script-support/src/coreProposalBehavior.js b/packages/deploy-script-support/src/coreProposalBehavior.js index 477e8dddd7a..02cf5a37843 100644 --- a/packages/deploy-script-support/src/coreProposalBehavior.js +++ b/packages/deploy-script-support/src/coreProposalBehavior.js @@ -8,7 +8,20 @@ const t = 'makeCoreProposalBehavior'; * @typedef {*} BootstrapPowers */ -// These permits apply to `allPowers` in `behavior` below. +/** + * @typedef {import('./externalTypes.js').ManifestBundleRef} ManifestBundleRef + * @typedef {[methodName: string, ...args: unknown[]]} FlatMethargs + * @typedef {Record>} Manifest + */ + +/** + * These permits are expected to be the minimum powers required by the + * `coreProposalBehavior` function returned from `makeCoreProposalBehavior`. + * They are merged with all of the manifest getter's permits to produce the + * total permits needed by the resulting core proposal (such as might be---and + * generally are---written into a *-permit.json file). + * @see {@link ./writeCoreProposal.js} + */ export const permits = { consume: { agoricNamesAdmin: t, vatAdminSvc: t, zoe: t }, evaluateBundleCap: t, @@ -23,22 +36,22 @@ export const permits = { * for catching bugs. Thus, this maker must not reference any other modules or * definitions. * - * @param {object} opts - * @param {{ bundleName: string } | { bundleID: string }} opts.manifestBundleRef - * @param {[string, ...unknown[]]} opts.getManifestCall - * @param {Record>} [opts.overrideManifest] - * @param {typeof import('@endo/far').E} opts.E - * @param {(...args: unknown[]) => void} [opts.log] - * @param {(ref: unknown) => Promise} [opts.restoreRef] + * @param {object} inputs + * @param {ManifestBundleRef} inputs.manifestBundleRef + * @param {FlatMethargs} inputs.getManifestCall + * @param {Manifest} [inputs.customManifest] + * @param {typeof import('@endo/far').E} inputs.E + * @param {(...args: unknown[]) => void} [inputs.log] + * @param {(ref: import('./externalTypes.js').ManifestBundleRef) => Promise>} [inputs.customRestoreRef] * @returns {(vatPowers: unknown) => Promise} */ export const makeCoreProposalBehavior = ({ manifestBundleRef, - getManifestCall, - overrideManifest, + getManifestCall: [manifestGetterName, ...manifestGetterArgs], + customManifest, E, log = console.info, - restoreRef: overrideRestoreRef, + customRestoreRef, }) => { const { entries, fromEntries } = Object; @@ -56,10 +69,32 @@ export const makeCoreProposalBehavior = ({ return fromEntries(ents); }; - /** @param {ChainBootstrapSpace & BootstrapPowers & { evaluateBundleCap: any }} allPowers */ - const behavior = async allPowers => { - // NOTE: If updating any of these names extracted from `allPowers`, you must - // change `permits` above to reflect their accessibility. + const makeRestoreRef = (vatAdminSvc, zoe) => { + /** @type {(ref: import('./externalTypes.js').ManifestBundleRef) => Promise>} */ + const defaultRestoreRef = async bundleRef => { + // extract-proposal.js creates these records, and bundleName is + // the optional name under which the bundle was installed into + // config.bundles + const bundleIdP = + 'bundleName' in bundleRef + ? E(vatAdminSvc).getBundleIDByName(bundleRef.bundleName) + : bundleRef.bundleID; + const bundleID = await bundleIdP; + const label = bundleID.slice(0, 8); + return E(zoe).installBundleID(bundleID, label); + }; + return defaultRestoreRef; + }; + + /** @param {ChainBootstrapSpace & BootstrapPowers & { evaluateBundleCap: any }} powers */ + const coreProposalBehavior = async powers => { + // NOTE: `powers` is expected to match or be a superset of the above `permits` export, + // which should therefore be kept in sync with this deconstruction code. + // HOWEVER, do note that this function is invoked with at least the *union* of powers + // required by individual moduleBehaviors declared by the manifest getter, which is + // necessary so it can use `runModuleBehaviors` to provide the appropriate subset to + // each one (see ./writeCoreProposal.js). + // Handle `powers` with the requisite care. const { consume: { vatAdminSvc, zoe, agoricNamesAdmin }, evaluateBundleCap, @@ -67,49 +102,45 @@ export const makeCoreProposalBehavior = ({ modules: { utils: { runModuleBehaviors }, }, - } = allPowers; - const [exportedGetManifest, ...manifestArgs] = getManifestCall; - - const defaultRestoreRef = async ref => { - // extract-proposal.js creates these records, and bundleName is - // the name under which the bundle was installed into - // config.bundles - const p = ref.bundleName - ? E(vatAdminSvc).getBundleIDByName(ref.bundleName) - : ref.bundleID; - const bundleID = await p; - const label = bundleID.slice(0, 8); - return E(zoe).installBundleID(bundleID, label); - }; - const restoreRef = overrideRestoreRef || defaultRestoreRef; + } = powers; // Get the on-chain installation containing the manifest and behaviors. - console.info('evaluateBundleCap', { + log('evaluateBundleCap', { manifestBundleRef, - exportedGetManifest, + manifestGetterName, vatAdminSvc, }); let bcapP; if ('bundleName' in manifestBundleRef) { bcapP = E(vatAdminSvc).getNamedBundleCap(manifestBundleRef.bundleName); - } else { + } else if ('bundleID' in manifestBundleRef) { bcapP = E(vatAdminSvc).getBundleCap(manifestBundleRef.bundleID); + } else { + const keys = Reflect.ownKeys(manifestBundleRef).map(key => + typeof key === 'string' ? JSON.stringify(key) : String(key), + ); + const keysStr = `[${keys.join(', ')}]`; + throw Error( + `bundleRef must have own bundleName or bundleID, missing in ${keysStr}`, + ); } const bundleCap = await bcapP; - const manifestNS = await evaluateBundleCap(bundleCap); + const proposalNS = await evaluateBundleCap(bundleCap); - console.error('execute', { - exportedGetManifest, - behaviors: Object.keys(manifestNS), + // Get the manifest and its metadata. + log('execute', { + manifestGetterName, + bundleExports: Object.keys(proposalNS), }); + const restoreRef = customRestoreRef || makeRestoreRef(vatAdminSvc, zoe); const { manifest, options: rawOptions, installations: rawInstallations, - } = await manifestNS[exportedGetManifest]( + } = await proposalNS[manifestGetterName]( harden({ restoreRef }), - ...manifestArgs, + ...manifestGetterArgs, ); // Await references in the options or installations. @@ -117,20 +148,24 @@ export const makeCoreProposalBehavior = ({ [rawOptions, rawInstallations].map(shallowlyFulfilled), ); - // Publish the installations for behavior dependencies. - const installAdmin = E(agoricNamesAdmin).lookupAdmin('installation'); - await Promise.all( - entries(installations || {}).map(([key, value]) => { - produceInstallations[key].resolve(value); - return E(installAdmin).update(key, value); - }), - ); + // Publish the installations for our dependencies. + const installationEntries = entries(installations || {}); + if (installationEntries.length > 0) { + const installAdmin = E(agoricNamesAdmin).lookupAdmin('installation'); + await Promise.all( + installationEntries.map(([key, value]) => { + produceInstallations[key].resolve(value); + return E(installAdmin).update(key, value); + }), + ); + } - // Evaluate the manifest for our behaviors. + // Evaluate the manifest. return runModuleBehaviors({ - allPowers, - behaviors: manifestNS, - manifest: overrideManifest || manifest, + // Remember that `powers` may be arbitrarily broad. + allPowers: powers, + behaviors: proposalNS, + manifest: customManifest || manifest, makeConfig: (name, _permit) => { log('coreProposal:', name); return { options }; @@ -138,22 +173,31 @@ export const makeCoreProposalBehavior = ({ }); }; - // Make the behavior the completion value. - return behavior; + return coreProposalBehavior; }; -export const makeEnactCoreProposalsFromBundleRef = - ({ makeCoreProposalArgs, E }) => - async allPowers => { +/** + * @param {object} inputs + * @param {Array<{ ref: ManifestBundleRef, call: FlatMethargs, customManifest?: Manifest }>} inputs.metadataRecords + * @param {typeof import('@endo/far').E} inputs.E + */ +export const makeEnactCoreProposalsFromBundleRef = ({ metadataRecords, E }) => { + /** + * @param {ChainBootstrapSpace & BootstrapPowers & { evaluateBundleCap: any }} powers + * @returns {Promise} + */ + const enactCoreProposals = async powers => { await Promise.all( - makeCoreProposalArgs.map(async ({ ref, call, overrideManifest }) => { - const subBehavior = makeCoreProposalBehavior({ + metadataRecords.map(async ({ ref, call, customManifest }) => { + const coreProposalBehavior = makeCoreProposalBehavior({ manifestBundleRef: ref, getManifestCall: call, - overrideManifest, + customManifest, E, }); - return subBehavior(allPowers); + return coreProposalBehavior(powers); }), ); }; + return enactCoreProposals; +}; diff --git a/packages/deploy-script-support/src/endo-pieces-contract.js b/packages/deploy-script-support/src/endo-pieces-contract.js index 419b243c133..715000aa415 100644 --- a/packages/deploy-script-support/src/endo-pieces-contract.js +++ b/packages/deploy-script-support/src/endo-pieces-contract.js @@ -18,6 +18,9 @@ export const start = () => { }); }; + /** + * @param {{ zoe: ERef }} opts + */ const makeBundler = ({ zoe }) => { /** @type { Map} */ const nameToContent = new Map(); diff --git a/packages/deploy-script-support/src/externalTypes.js b/packages/deploy-script-support/src/externalTypes.js index 35535dcf66a..364149da559 100644 --- a/packages/deploy-script-support/src/externalTypes.js +++ b/packages/deploy-script-support/src/externalTypes.js @@ -16,30 +16,29 @@ export {}; */ /** - * @typedef BundleHandle - * @property {string} [bundleName] + * @typedef {{fileName?: string} & ({ bundleName: string } | { bundleID: string}) } ManifestBundleRef */ /** * @callback PublishBundleRef - * @param {ERef} bundle - * @returns {Promise} + * @param {ERef} bundle + * @returns {Promise} */ /** - * @callback InstallBundle + * @callback InstallEntrypoint * @param {string} srcSpec - * @param {string} bundlePath - * @param {any} [opts] - * @returns {BundleHandle} + * @param {string} [bundlePath] + * @param {unknown} [opts] + * @returns {Promise} */ /** * @callback ProposalBuilder * @param {{ * publishRef: PublishBundleRef, - * install: InstallBundle, - * wrapInstall?: (f: T) => T } + * install: InstallEntrypoint, + * wrapInstall?: (f: T) => T } * } powers * @param {...any} args * @returns {Promise} diff --git a/packages/deploy-script-support/src/extract-proposal.js b/packages/deploy-script-support/src/extract-proposal.js index ea539b145b0..1fb77e553a2 100644 --- a/packages/deploy-script-support/src/extract-proposal.js +++ b/packages/deploy-script-support/src/extract-proposal.js @@ -14,7 +14,7 @@ import { * @typedef {string | { module: string, entrypoint: string, args?: Array }} ConfigProposal */ -const { details: X, Fail } = assert; +const { Fail } = assert; const req = createRequire(import.meta.url); @@ -24,8 +24,8 @@ const req = createRequire(import.meta.url); * @typedef {string} FilePath */ const pathResolve = (...paths) => { - const fileName = paths.pop(); - assert(fileName, '>=1 paths required'); + const fileName = /** @type {string} */ (paths.pop()); + fileName || Fail`base name required`; try { return req.resolve(fileName, { paths, @@ -40,6 +40,19 @@ const findModule = (initDir, srcSpec) => ? pathResolve(initDir, srcSpec) : req.resolve(srcSpec); +/** + * @param {{ bundleID?: string, bundleName?: string }} handle - mutated then hardened + * @param {string} sourceSpec - the specifier of a module to load + * @param {number} sequence - the sequence number of the proposal + * @param {string} piece - the piece of the proposal + * @returns {Promise<[string, any]>} + */ +const namedHandleToBundleSpec = async (handle, sourceSpec, sequence, piece) => { + handle.bundleName = `coreProposal${sequence}_${piece}`; + harden(handle); + return harden([handle.bundleName, { sourceSpec }]); +}; + /** * Format core proposals to be run at bootstrap: * SwingSet `bundles` configuration @@ -54,26 +67,28 @@ const findModule = (initDir, srcSpec) => * @param {ConfigProposal[]} coreProposals - governance * proposals to run at chain bootstrap for scenarios such as sim-chain. * @param {FilePath} [dirname] - * @param {typeof makeEnactCoreProposalsFromBundleRef} [makeEnactCoreProposals] - * @param {(i: number) => number} [getSequenceForProposal] + * @param {object} [opts] + * @param {typeof makeEnactCoreProposalsFromBundleRef} [opts.makeEnactCoreProposals] + * @param {(i: number) => number} [opts.getSequenceForProposal] + * @param {typeof namedHandleToBundleSpec} [opts.handleToBundleSpec] */ export const extractCoreProposalBundles = async ( coreProposals, dirname = '.', - makeEnactCoreProposals = makeEnactCoreProposalsFromBundleRef, - getSequenceForProposal, + opts, ) => { - if (!getSequenceForProposal) { - // Deterministic proposal numbers. - getSequenceForProposal = i => i; - } + const { + makeEnactCoreProposals = makeEnactCoreProposalsFromBundleRef, + getSequenceForProposal = i => i, + handleToBundleSpec = namedHandleToBundleSpec, + } = opts || {}; dirname = pathResolve(dirname); dirname = await fs.promises .stat(dirname) .then(stbuf => (stbuf.isDirectory() ? dirname : path.dirname(dirname))); - /** @type {Map<{ bundleName?: string }, { source: string, bundle?: string }>} */ + /** @type {Map<{ bundleID?: string, bundleName?: string }, { source: string, bundle?: string }>} */ const bundleHandleToAbsolutePaths = new Map(); const bundleToSource = new Map(); @@ -117,11 +132,8 @@ export const extractCoreProposalBundles = async ( absolutePaths.bundle = absoluteBundle; const oldSource = bundleToSource.get(absoluteBundle); if (oldSource) { - assert.equal( - oldSource, - absoluteSrc, - X`${bundlePath} already installed from ${oldSource}, now ${absoluteSrc}`, - ); + oldSource === absoluteSrc || + Fail`${bundlePath} already installed from ${oldSource}, now ${absoluteSrc}`; } else { bundleToSource.set(absoluteBundle, absoluteSrc); } @@ -135,36 +147,46 @@ export const extractCoreProposalBundles = async ( /** @type {import('./externalTypes.js').PublishBundleRef} */ const publishRef = async handleP => { const handle = await handleP; + // eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error -- https://github.com/Agoric/agoric-sdk/issues/4620 */ + // @ts-ignore xxx types bundleHandleToAbsolutePaths.has(handle) || Fail`${handle} not in installed bundles`; return handle; }; - const proposal = await ns[entrypoint]({ publishRef, install }, ...args); + const proposal = await ns[entrypoint]( + { + publishRef, + // @ts-expect-error not statically verified to return a full obj + install, + }, + ...args, + ); // Add the proposal bundle handles in sorted order. - const bundleSpecEntries = [...thisProposalBundleHandles.keys()] - .map(handle => [handle, bundleHandleToAbsolutePaths.get(handle)]) - .sort(([_hnda, { source: a }], [_hndb, { source: b }]) => { - if (a < b) { - return -1; - } - if (a > b) { - return 1; - } - return 0; - }) - .map(([handle, absolutePaths], j) => { - // Transform the bundle handle identity into a bundleName reference. - handle.bundleName = `coreProposal${thisProposalSequence}_${j}`; - harden(handle); - - /** @type {[string, { sourceSpec: string }]} */ - const specEntry = [ - handle.bundleName, - { sourceSpec: absolutePaths.source }, - ]; - return specEntry; - }); + const bundleSpecEntries = await Promise.all( + [...thisProposalBundleHandles.keys()] + .map(handle => [handle, bundleHandleToAbsolutePaths.get(handle)]) + .sort(([_hnda, { source: a }], [_hndb, { source: b }]) => { + if (a < b) { + return -1; + } + if (a > b) { + return 1; + } + return 0; + }) + .map(async ([handle, absolutePaths], j) => { + // Transform the bundle handle identity into a bundleName reference. + const specEntry = await handleToBundleSpec( + handle, + absolutePaths.source, + thisProposalSequence, + String(j), + ); + harden(handle); + return specEntry; + }), + ); // Now that we've assigned all the bundleNames and hardened the // handles, we can extract the behavior bundle. @@ -172,33 +194,36 @@ export const extractCoreProposalBundles = async ( harden(proposal), ); - const behaviorSource = pathResolve(initDir, sourceSpec); - const behaviors = await import(behaviorSource); - const [exportedGetManifest, ...manifestArgs] = getManifestCall; - const { manifest: overrideManifest } = await behaviors[ - exportedGetManifest - ](harden({ restoreRef: () => null }), ...manifestArgs); + const proposalSource = pathResolve(initDir, sourceSpec); + const proposalNS = await import(proposalSource); + const [manifestGetterName, ...manifestGetterArgs] = getManifestCall; + manifestGetterName in proposalNS || + Fail`proposal ${proposalSource} missing export ${manifestGetterName}`; + const { manifest: customManifest } = await proposalNS[manifestGetterName]( + harden({ restoreRef: () => null }), + ...manifestGetterArgs, + ); - const behaviorBundleHandle = harden({ - bundleName: `coreProposal${thisProposalSequence}_behaviors`, - }); - const behaviorAbsolutePaths = harden({ - source: behaviorSource, - }); - bundleHandleToAbsolutePaths.set( + const behaviorBundleHandle = {}; + const specEntry = await handleToBundleSpec( behaviorBundleHandle, - behaviorAbsolutePaths, + proposalSource, + thisProposalSequence, + 'behaviors', ); + bundleSpecEntries.unshift(specEntry); - bundleSpecEntries.unshift([ - behaviorBundleHandle.bundleName, - { sourceSpec: behaviorAbsolutePaths.source }, - ]); + bundleHandleToAbsolutePaths.set( + behaviorBundleHandle, + harden({ + source: proposalSource, + }), + ); return harden({ ref: behaviorBundleHandle, call: getManifestCall, - overrideManifest, + customManifest, bundleSpecs: bundleSpecEntries, }); }), @@ -211,22 +236,29 @@ export const extractCoreProposalBundles = async ( harden(bundles); // Extract the manifest references and calls. - const makeCPArgs = extracted.map(({ ref, call, overrideManifest }) => ({ + const metadataRecords = extracted.map(({ ref, call, customManifest }) => ({ ref, call, - overrideManifest, + customManifest, })); - harden(makeCPArgs); + harden(metadataRecords); const code = `\ -// This is generated by @agoric/cosmic-swingset/src/extract-proposal.js - DO NOT EDIT +// This is generated by @agoric/deploy-script-support/src/extract-proposal.js - DO NOT EDIT /* eslint-disable */ -const makeCoreProposalArgs = harden(${stringify(makeCPArgs, true)}); - -const makeCoreProposalBehavior = ${makeCoreProposalBehavior}; +const metadataRecords = harden(${stringify(metadataRecords, true)}); -(${makeEnactCoreProposals})({ makeCoreProposalArgs, E }); +// Make an enactCoreProposals function and "export" it by way of script completion value. +// It is constructed by an IIFE to ensure the absence of global bindings for +// makeCoreProposalBehavior and makeEnactCoreProposals (the latter referencing the former), +// which may not be necessary but preserves behavior pre-dating +// https://github.com/Agoric/agoric-sdk/pull/8712 . +const enactCoreProposals = (( + makeCoreProposalBehavior = ${makeCoreProposalBehavior}, + makeEnactCoreProposals = ${makeEnactCoreProposals}, +) => makeEnactCoreProposals({ metadataRecords, E }))(); +enactCoreProposals; `; // console.debug('created bundles from proposals:', coreProposals, bundles); diff --git a/packages/deploy-script-support/src/getBundlerMaker.js b/packages/deploy-script-support/src/getBundlerMaker.js index 375d7c331b7..425b49471a9 100644 --- a/packages/deploy-script-support/src/getBundlerMaker.js +++ b/packages/deploy-script-support/src/getBundlerMaker.js @@ -13,6 +13,9 @@ import { E } from '@endo/far'; import url from 'url'; +/** @typedef {ReturnType['publicFacet']} BundleMaker */ +/** @typedef {ReturnType} Bundler */ + export const makeGetBundlerMaker = (homeP, { lookup, bundleSource }) => async ({ @@ -20,6 +23,7 @@ export const makeGetBundlerMaker = log = console.log, } = {}) => { const { board: optionalBoard, zoe, scratch } = await homeP; + /** @type {() => Promise} */ const lookupOrCreate = async () => { // Locate the bundler maker if any already exists at the given path. let bundlerMaker = await lookup(JSON.parse(BUNDLER_MAKER_LOOKUP)); diff --git a/packages/deploy-script-support/src/writeCoreProposal.js b/packages/deploy-script-support/src/writeCoreProposal.js index 57e34e9fe05..768ba5dce0c 100644 --- a/packages/deploy-script-support/src/writeCoreProposal.js +++ b/packages/deploy-script-support/src/writeCoreProposal.js @@ -7,6 +7,25 @@ import { createBundles } from '@agoric/internal/src/node/createBundles.js'; import { defangAndTrim, mergePermits, stringify } from './code-gen.js'; import { makeCoreProposalBehavior, permits } from './coreProposalBehavior.js'; +/** + * @callback WriteCoreProposal + * @param {string} filePrefix + * @param {import('./externalTypes.js').ProposalBuilder} proposalBuilder + * @returns {Promise} + */ + +/** + * + * @param {*} homeP + * @param {*} endowments + * @param {{ + * getBundlerMaker: () => Promise, + * getBundleSpec: (...args: *) => Promise, + * log?: typeof console.log, + * writeFile?: typeof fs.promises.writeFile + * }} io + * @returns {WriteCoreProposal} + */ export const makeWriteCoreProposal = ( homeP, endowments, @@ -20,6 +39,7 @@ export const makeWriteCoreProposal = ( const { bundleSource, pathResolve } = endowments; let bundlerCache; + /** @returns {import('./getBundlerMaker.js').Bundler} */ const getBundler = () => { if (!bundlerCache) { bundlerCache = E(getBundlerMaker()).makeBundler({ @@ -32,15 +52,15 @@ export const makeWriteCoreProposal = ( const mergeProposalPermit = async (proposal, additionalPermits) => { const { sourceSpec, - getManifestCall: [exportedGetManifest, ...manifestArgs], + getManifestCall: [manifestGetterName, ...manifestGetterArgs], } = proposal; - const manifestNs = await import(pathResolve(sourceSpec)); + const proposalNS = await import(pathResolve(sourceSpec)); // We only care about the manifest, not any restoreRef calls. - const { manifest } = await manifestNs[exportedGetManifest]( - { restoreRef: x => `restoreRef:${x}` }, - ...manifestArgs, + const { manifest } = await proposalNS[manifestGetterName]( + harden({ restoreRef: x => `restoreRef:${x}` }), + ...manifestGetterArgs, ); const mergedPermits = mergePermits(manifest); @@ -50,8 +70,18 @@ export const makeWriteCoreProposal = ( }; }; - let mutex = Promise.resolve(); + let mutex = + /** @type {Promise} */ ( + Promise.resolve() + ); + /** @type {WriteCoreProposal} */ const writeCoreProposal = async (filePrefix, proposalBuilder) => { + /** + * + * @param {string} entrypoint + * @param {string} [bundlePath] + * @returns {Promise} + */ const getBundle = async (entrypoint, bundlePath) => { if (!bundlePath) { return bundleSource(pathResolve(entrypoint)); @@ -62,20 +92,36 @@ export const makeWriteCoreProposal = ( return ns.default; }; - // Install an entrypoint. + const bundles = []; + + /** + * Install an entrypoint. + * + * @param {string} entrypoint + * @param {string} [bundlePath] + * @param {unknown} [opts] + * @returns {Promise} + */ const install = async (entrypoint, bundlePath, opts) => { const bundle = getBundle(entrypoint, bundlePath); // Serialise the installations. - mutex = E.when(mutex, () => { + mutex = E.when(mutex, async () => { // console.log('installing', { filePrefix, entrypoint, bundlePath }); - return getBundleSpec(bundle, getBundler, opts); + const spec = await getBundleSpec(bundle, getBundler, opts); + bundles.push({ + entrypoint, + ...spec, + }); + return spec; }); + // @ts-expect-error xxx mutex type narrowing return mutex; }; // Await a reference then publish to the board. const cmds = []; + /** @param {Promise} refP */ const publishRef = async refP => { const { fileName, ...ref } = await refP; if (fileName) { @@ -93,7 +139,7 @@ export const makeWriteCoreProposal = ( // console.log('created', { filePrefix, sourceSpec, getManifestCall }); // Extract the top-level permit. - const { permits: proposalPermit, manifest: overrideManifest } = + const { permits: proposalPermit, manifest: customManifest } = await mergeProposalPermit(proposal, permits); // Get an install @@ -106,10 +152,14 @@ export const makeWriteCoreProposal = ( const manifestBundleRef = ${stringify(manifestBundleRef)}; const getManifestCall = harden(${stringify(getManifestCall, true)}); -const overrideManifest = ${stringify(overrideManifest, true)}; - -// Make the behavior the completion value. -(${makeCoreProposalBehavior})({ manifestBundleRef, getManifestCall, overrideManifest, E }); +const customManifest = ${stringify(customManifest, true)}; + +// Make a behavior function and "export" it by way of script completion value. +// It is constructed by an anonymous invocation to ensure the absence of a global binding +// for makeCoreProposalBehavior, which may not be necessary but preserves behavior pre-dating +// https://github.com/Agoric/agoric-sdk/pull/8712 . +const behavior = (${makeCoreProposalBehavior})({ manifestBundleRef, getManifestCall, customManifest, E }); +behavior; `; const trimmed = defangAndTrim(code); @@ -125,6 +175,18 @@ const overrideManifest = ${stringify(overrideManifest, true)}; log(`creating ${proposalJsFile}`); await writeFile(proposalJsFile, trimmed); + const plan = { + name: filePrefix, + script: proposalJsFile, + permit: proposalPermitJsonFile, + bundles, + }; + + await writeFile( + `${filePrefix}-plan.json`, + `${JSON.stringify(plan, null, 2)}\n`, + ); + log(`\ You can now run a governance submission command like: agd tx gov submit-proposal swingset-core-eval ${proposalPermitJsonFile} ${proposalJsFile} \\ diff --git a/packages/deploy-script-support/test/unitTests/test-getBundlerMaker.js b/packages/deploy-script-support/test/unitTests/test-getBundlerMaker.js index 53875a9fa7d..bea7a2cb5d4 100644 --- a/packages/deploy-script-support/test/unitTests/test-getBundlerMaker.js +++ b/packages/deploy-script-support/test/unitTests/test-getBundlerMaker.js @@ -15,10 +15,12 @@ test('getBundlerMaker - already made', async t => { const getBundlerMaker = makeGetBundlerMaker({}, { lookup }); const bundler = await getBundlerMaker({ log: t.log }); + // @ts-expect-error mock lookup result t.is(bundler, 'BUNDLER_MAKER_FOUND'); }); test('getBundlerMaker - not yet made', async t => { + /** @type {any} */ let bundlerMaker; const zoe = { install: async b => { diff --git a/packages/deploy-script-support/test/unitTests/test-installInPieces.js b/packages/deploy-script-support/test/unitTests/test-installInPieces.js index 35c98459763..08181ce0689 100644 --- a/packages/deploy-script-support/test/unitTests/test-installInPieces.js +++ b/packages/deploy-script-support/test/unitTests/test-installInPieces.js @@ -27,6 +27,7 @@ test('installInPieces', async t => { }, }; + // @ts-expect-error fake Zoe const bundler = E(publicFacet).makeBundler({ zoe }); const installation = await installInPieces(endoPieces, bundler, { diff --git a/packages/inter-protocol/scripts/add-collateral-core.js b/packages/inter-protocol/scripts/add-collateral-core.js index bad11ee1c15..94e2425075b 100644 --- a/packages/inter-protocol/scripts/add-collateral-core.js +++ b/packages/inter-protocol/scripts/add-collateral-core.js @@ -107,6 +107,7 @@ export default async (homeP, endowments) => { await writeCoreProposal('gov-add-collateral', defaultProposalBuilder); await writeCoreProposal('gov-start-psm', opts => + // @ts-expect-error XXX makeInstallCache types psmProposalBuilder({ ...opts, wrapInstall: tool.wrapInstall }), ); }; diff --git a/packages/inter-protocol/scripts/init-core.js b/packages/inter-protocol/scripts/init-core.js index 332ec3d7030..7bf6c282b49 100644 --- a/packages/inter-protocol/scripts/init-core.js +++ b/packages/inter-protocol/scripts/init-core.js @@ -188,9 +188,11 @@ export default async (homeP, endowments) => { }); await Promise.all([ writeCoreProposal('gov-econ-committee', opts => + // @ts-expect-error XXX makeInstallCache types committeeProposalBuilder({ ...opts, wrapInstall: tool.wrapInstall }), ), writeCoreProposal('gov-amm-vaults-etc', opts => + // @ts-expect-error XXX makeInstallCache types mainProposalBuilder({ ...opts, wrapInstall: tool.wrapInstall }), ), ]); diff --git a/packages/inter-protocol/test/test-gov-collateral.js b/packages/inter-protocol/test/test-gov-collateral.js deleted file mode 100644 index d476115fbba..00000000000 --- a/packages/inter-protocol/test/test-gov-collateral.js +++ /dev/null @@ -1,605 +0,0 @@ -/** @file currently disabled https://github.com/Agoric/agoric-sdk/issues/7175 */ -import { test as anyTest } from '@agoric/swingset-vat/tools/prepare-test-env-ava.js'; -import process from 'process'; -import url from 'url'; -import path from 'path'; -import { E, Far } from '@endo/far'; -import { makePromiseKit } from '@endo/promise-kit'; -import bundleSource from '@endo/bundle-source'; -import { - addBankAssets, - makeAddressNameHubs, - makeOracleBrands, - makeBoard, - startPriceAuthorityRegistry, -} from '@agoric/vats/src/core/basic-behaviors.js'; -import centralSupplyBundle from '@agoric/vats/bundles/bundle-centralSupply.js'; -import { - bridgeCoreEval, - setupClientManager, -} from '@agoric/vats/src/core/chain-behaviors.js'; -import { extractCoreProposalBundles } from '@agoric/deploy-script-support/src/extract-proposal.js'; -import { makeCoreProposalBehavior } from '@agoric/deploy-script-support/src/coreProposalBehavior.js'; -import { makeNameHubKit } from '@agoric/vats/src/nameHub.js'; -import { AmountMath, makeIssuerKit } from '@agoric/ertp'; -import { Stable } from '@agoric/vats/src/tokens.js'; -import { provideBundleCache } from '@agoric/swingset-vat/tools/bundleTool.js'; -import { TimeMath } from '@agoric/time'; - -import { makeScalarBigMapStore } from '@agoric/vat-data'; -import { - setupBootstrap, - setUpZoeForTest, - mintRunPayment, - DENOM_UNIT as UNIT, -} from './supports.js'; -import { INVITATION_MAKERS_DESC } from '../src/econCommitteeCharter.js'; - -/** @template T @typedef {import('@endo/promise-kit').PromiseKit} PromiseKit */ - -const { Fail } = assert; -const dirname = url.fileURLToPath(new URL('.', import.meta.url)); - -/** @type {import('ava').TestFn>>} */ -const test = anyTest; - -const contractRoots = { - mintHolder: '../vats/src/mintHolder.js', - econCommitteeCharter: './src/econCommitteeCharter.js', -}; - -const coreProposals = { - addCollateral: '../scripts/add-collateral-core.js', - startRunPreview: '../scripts/init-core.js', - inviteCommittee: '../scripts/invite-committee-core.js', -}; - -const voterAddresses = { - Rowland: `agoric1qed57ae8k5cqr30u5mmd46jdxfr0juyggxv6ad`, - Bill: `agoric1xgw4cknedau6xhrlyn6c8e40d02mejee8gwnef`, - Dan: `agoric1yumvyl7f5nkalss7w59gs6n3jtqv5gmarudx55`, -}; - -// Nondeterministic, but the test shouldn't rely on this value. -let lastProposalSequence = 0; - -const makeTestContext = async () => { - const bundleCache = await provideBundleCache('bundles/', {}, s => import(s)); - const { zoe, feeMintAccessP, vatAdminSvc, vatAdminState } = - await setUpZoeForTest(); - assert(vatAdminState); - - const stableIssuer = await E(zoe).getFeeIssuer(); - const stableBrand = await E(stableIssuer).getBrand(); - - const install = (src, dest) => - bundleCache.load(src, dest).then(b => E(zoe).install(b)); - const installation = { - mintHolder: install(contractRoots.mintHolder, 'mintHolder'), - /** @type {Promise>} */ - centralSupply: E(zoe).install(centralSupplyBundle), - econCommitteeCharter: install( - contractRoots.econCommitteeCharter, - 'econCommitteeCharter', - ), - }; - - const bundleNameToAbsolutePaths = new Map(); - const bundlePathToInstallP = new Map(); - const restoreBundleName = bundleName => { - const absolutePaths = bundleNameToAbsolutePaths.get(bundleName); - absolutePaths || Fail`bundleName ${bundleName} not found`; - const { source, bundle } = absolutePaths; - const bundlePath = bundle || source.replace(/(\\|\/|:)/g, '_'); - if (!bundlePathToInstallP.has(bundlePath)) { - const match = path.basename(bundlePath).match(/^bundle-(.*)\.js$/); - const actualBundle = match ? match[1] : bundlePath; - bundlePathToInstallP.set(bundlePath, install(source, actualBundle)); - } - return bundlePathToInstallP.get(bundlePath); - }; - - const registerOne = async (bundleName, paths) => { - !bundleNameToAbsolutePaths.has(bundleName) || - Fail`bundleName ${bundleName} already registered`; - bundleNameToAbsolutePaths.set(bundleName, paths); - // use vatAdminState to install this bundle - let bundleP; - if (paths.bundle) { - bundleP = import(paths.bundle).then(ns => ns.default); - } else { - assert(paths.source); - bundleP = bundleSource(paths.source); - } - const bundle = await bundleP; - const bundleID = bundle.endoZipBase64Sha512; - assert(bundleID); - vatAdminState.installNamedBundle(bundleName, bundleID, bundle); - }; - - const registerBundleHandles = async bundleHandleMap => { - const allP = []; - for (const [{ bundleName }, paths] of bundleHandleMap.entries()) { - allP.push(registerOne(bundleName, paths)); - } - await Promise.all(allP); - }; - - return { - registerBundleHandles, - restoreBundleName, - cleanups: [], - zoe: await zoe, - feeMintAccess: await feeMintAccessP, - vatAdminSvc, - vatAdminState, - run: { issuer: stableIssuer, brand: stableBrand }, - installation, - }; -}; - -test.before(async t => { - t.context = await makeTestContext(); -}); - -/** - * @param {import('ava').ExecutionContext>>} t - * @param {{ env?: Record }} [io] - */ -const makeScenario = async (t, { env = process.env } = {}) => { - const rawSpace = await setupBootstrap(t); - const vatPowers = t.context.vatAdminState.getVatPowers(); - const space = { vatPowers, ...rawSpace }; - space.produce.vatAdminSvc.resolve(t.context.vatAdminSvc); - - const loadVat = name => { - const baggage = makeScalarBigMapStore('baggage'); - return import(`@agoric/vats/src/vat-${name}.js`).then(ns => - ns.buildRootObject({}, {}, baggage), - ); - }; - space.produce.loadVat.resolve(loadVat); - space.produce.loadCriticalVat.resolve(loadVat); - - const emptyRunPayment = async () => { - const { - issuer: { - consume: { [Stable.symbol]: stableIssuer }, - }, - brand: { - consume: { [Stable.symbol]: stableBrand }, - }, - } = space; - return E(E(stableIssuer).makeEmptyPurse()).withdraw( - AmountMath.make(await stableBrand, 0n), - ); - }; - - /** @type {PromiseKit<{ mint: ERef, issuer: ERef, brand: Brand}>} */ - const ibcKitP = makePromiseKit(); - - const startDevNet = async () => { - // If we don't have a proper bridge manager, we need it to be undefined. - space.produce.bridgeManager.resolve(undefined); - - /** @type {BankManager} */ - const bankManager = Far('mock BankManager', { - getAssetSubscription: () => assert.fail('not impl'), - getModuleAccountAddress: () => assert.fail('not impl'), - getRewardDistributorDepositFacet: () => - Far('depositFacet', { - receive: () => /** @type {any} */ (null), - }), - addAsset: async (denom, keyword, proposedName, kit) => { - t.log('addAsset', { denom, keyword, issuer: `${kit.issuer}` }); - t.truthy(kit.mint); - ibcKitP.resolve({ ...kit, mint: kit.mint || assert.fail() }); - }, - getBankForAddress: () => assert.fail('not impl'), - }); - space.produce.bankManager.resolve(bankManager); - - space.installation.produce.mintHolder.resolve( - t.context.installation.mintHolder, - ); - - space.produce.initialSupply.resolve(emptyRunPayment()); - - return Promise.all([ - // @ts-expect-error TODO: align types better - addBankAssets(space), - setupClientManager(space), - makeAddressNameHubs(space), - // @ts-expect-error TODO: align types better - makeBoard(space), - // @ts-expect-error TODO: align types better - makeOracleBrands(space), - // @ts-expect-error TODO: align types better - bridgeCoreEval(space), - // @ts-expect-error TODO: align types better - startPriceAuthorityRegistry(space), - ]); - }; - - const provisionMembers = async () => { - const { zoe } = space.consume; - const invitationIssuer = await E(zoe).getInvitationIssuer(); - const nameAdmin = await space.consume.namesByAddressAdmin; - const purses = new Map( - Object.values(voterAddresses).map(addr => { - const purse = E(invitationIssuer).makeEmptyPurse(); - return [addr, purse]; - }), - ); - Object.values(voterAddresses).forEach(addr => { - const { nameHub, nameAdmin: myAddressNameAdmin } = makeNameHubKit(); - const depositFacet = Far('depositFacet', { - receive: pmt => { - const purse = purses.get(addr); - assert(purse, addr); - return E(purse).deposit(pmt); - }, - }); - myAddressNameAdmin.update('depositFacet', depositFacet); - nameAdmin.update(addr, nameHub, myAddressNameAdmin); - }); - return purses; - }; - - /** @type {any} */ - const { restoreBundleName: produceRestoreBundleName } = space.produce; - produceRestoreBundleName.resolve(t.context.restoreBundleName); - const makeEnactCoreProposalsFromBundleHandle = - ({ makeCoreProposalArgs, E: cpE }) => - async allPowers => { - const { - consume: { restoreBundleName }, - } = allPowers; - const restoreRef = async ({ bundleName }) => { - return cpE(restoreBundleName)(bundleName); - }; - - await Promise.all( - makeCoreProposalArgs.map(async ({ ref, call, overrideManifest }) => { - const subBehavior = makeCoreProposalBehavior({ - manifestBundleRef: ref, - getManifestCall: call, - overrideManifest, - E: cpE, - restoreRef, - }); - await subBehavior(allPowers); - }), - ); - }; - - /** - * @param {string[]} proposals - */ - const evalProposals = async proposals => { - const { code, bundleHandleToAbsolutePaths } = - await extractCoreProposalBundles( - proposals, - dirname, - makeEnactCoreProposalsFromBundleHandle, - () => (lastProposalSequence += 1), - ); - await t.context.registerBundleHandles(bundleHandleToAbsolutePaths); - - const coreEvalMessage = { - type: 'CORE_EVAL', - evals: [ - { - json_permits: 'true', - js_code: code, - }, - ], - }; - - /** @type {any} */ - const { coreEvalBridgeHandler } = space.consume; - await E(coreEvalBridgeHandler).fromBridge(coreEvalMessage); - }; - - const startRunPreview = async () => { - const { brand: atomBrand } = makeIssuerKit( - 'ATOM', - undefined, - harden({ decimalPlaces: 6 }), - ); - env.MIN_INITIAL_POOL_LIQUIDITY = '0'; - await Promise.all([ - E(E(space.consume.agoricNamesAdmin).lookupAdmin('oracleBrand')).update( - 'ATOM', - atomBrand, - ), - evalProposals([coreProposals.startRunPreview]), - ]); - }; - - const enactVaultAssetProposal = async () => { - env.INTERCHAIN_DENOM = 'ibc/abc123'; - await evalProposals([coreProposals.addCollateral]); - }; - - const enactInviteEconCommitteeProposal = async () => { - env.ECON_COMMITTEE_ADDRESSES = JSON.stringify(voterAddresses); - await evalProposals([coreProposals.inviteCommittee]); - }; - - /** - * @param {{ - * agoricNames: ERef, - * board: ERef, - * zoe: ERef, - * wallet: { - * purses: { - * ist: ERef, - * atom: ERef, - * }, - * }, - * }} home - */ - const makeBenefactor = home => { - const { - agoricNames, - zoe, - wallet: { purses }, - } = home; - - return Far('benefactor', { - depositInReserve: async (qty = 10_000n) => { - const atomBrand = await E(agoricNames).lookup('brand', 'ATOM'); - /** @type {ERef} */ - const reserveAPI = E(zoe).getPublicFacet( - E(agoricNames).lookup('instance', 'reserve'), - ); - - const proposal = harden({ - give: { Collateral: AmountMath.make(atomBrand, qty * UNIT) }, - }); - const atom10k = await E(purses.atom).withdraw(proposal.give.Collateral); - const seat = E(zoe).offer( - await E(reserveAPI).makeAddCollateralInvitation(), - proposal, - harden({ Collateral: atom10k }), - ); - return E(seat).getOfferResult(); - }, - }); - }; - - const { agoricNames, zoe, board } = space.consume; - const makeRunPurse = async value => { - /** @type {Promise>} */ - const issuerP = E(agoricNames).lookup('issuer', 'IST'); - const purseP = E(issuerP).makeEmptyPurse(); - return mintRunPayment(value, { - centralSupply: t.context.installation.centralSupply, - feeMintAccess: t.context.feeMintAccess, - zoe, - }).then(pmt => - E(purseP) - .deposit(pmt) - .then(_ => purseP), - ); - }; - const makeAtomPurse = async value => { - // when using benefactor.makePool: - // const { issuer, mint, brand } = await ibcKitP.promise; - const kits = await E(space.consume.contractKits).values(); - /** @type {{ creatorFacet: ERef> }} */ - // @ts-expect-error cast - const { creatorFacet: mint } = - [...kits].find(k => k.label === 'mintHolder') || Fail`no mintHolder`; - const issuer = E(mint).getIssuer(); - const purseP = E(issuer).makeEmptyPurse(); - const brand = await E(issuer).getBrand(); - const pmt = await E(mint).mintPayment(AmountMath.make(brand, value)); - await E(purseP).deposit(pmt); - return purseP; - }; - const purses = { - ist: makeRunPurse(10_000n * UNIT), - atom: makeAtomPurse(10_000n * UNIT), - }; - - return { - startDevNet, - provisionMembers, - startRunPreview, - enactVaultAssetProposal, - enactInviteEconCommitteeProposal, - benefactor: makeBenefactor({ agoricNames, board, zoe, wallet: { purses } }), - space, - }; -}; - -test.skip('Benefactor can add to reserve', async t => { - const s = await makeScenario(t); - await s.startDevNet(); - await s.provisionMembers(); - await s.startRunPreview(); - - await Promise.all([ - s.enactVaultAssetProposal(), - s.enactInviteEconCommitteeProposal(), - ]); - - const result = await s.benefactor.depositInReserve(4000n); - t.deepEqual(result, 'added Collateral to the Reserve'); -}); - -test.skip('voters get invitations', async t => { - const s = await makeScenario(t); - await s.startDevNet(); - const purses = await s.provisionMembers(); - await s.startRunPreview(); - - await Promise.all([ - s.enactVaultAssetProposal(), - s.enactInviteEconCommitteeProposal(), - ]); - - t.is(purses.size, 3); - await Promise.all( - [...purses].map(async ([_addr, purse]) => { - const amt = await E(purse).getCurrentAmount(); - const value = amt.value; - assert(Array.isArray(value)); - - const instanceInv = value.find( - ({ description }) => description === INVITATION_MAKERS_DESC, - ); - t.assert(instanceInv); - - const voterInv = value.find(({ description }) => - description.startsWith('Voter'), - ); - t.assert(voterInv); - t.not(instanceInv, voterInv); - }), - ); -}); - -test.skip('assets are in Vaults', async t => { - const s = await makeScenario(t); - await s.startDevNet(); - await s.provisionMembers(); - await s.startRunPreview(); - // await s.benefactor.makePool(2000n, 1000n); - - await Promise.all([ - s.enactVaultAssetProposal(), - s.enactInviteEconCommitteeProposal(), - ]); - - const { - consume: { zoe, agoricNames }, - instance: { consume: instanceP }, - } = s.space; - const brand = await E(agoricNames).lookup('brand', 'ATOM'); - const stableBrand = await E(agoricNames).lookup('brand', Stable.symbol); - - /** @type {ERef} */ - const vaultsAPI = instanceP.VaultFactory.then(i => E(zoe).getPublicFacet(i)); - - const params = await E(vaultsAPI).getGovernedParams({ - collateralBrand: brand, - }); - t.deepEqual(params.DebtLimit, { - type: 'amount', - // 1000 IST is the default debtLimitValue in add-collateral-core - value: { brand: stableBrand, value: 1_000n * UNIT }, - }); -}); - -test.skip('Committee can raise debt limit', async t => { - const s = await makeScenario(t); - await s.startDevNet(); - const invitationPurses = await s.provisionMembers(); - await s.startRunPreview(); - // await s.benefactor.makePool(2000n, 1000n); - - await Promise.all([ - s.enactVaultAssetProposal(), - s.enactInviteEconCommitteeProposal(), - ]); - - const { agoricNames } = s.space.consume; - const brand = await E(agoricNames).lookup('brand', 'ATOM'); - const stableBrand = await E(agoricNames).lookup('brand', Stable.symbol); - const vaultsInstance = await E(agoricNames).lookup( - 'instance', - 'VaultFactory', - ); - const economicCommittee = await E(agoricNames).lookup( - 'instance', - 'economicCommittee', - ); - - const { zoe } = s.space.consume; - t.log({ purses: invitationPurses }); - - const billsInvitationPurse = invitationPurses.get(voterAddresses.Bill); - assert(billsInvitationPurse); - - const invitationsAmt = await E(billsInvitationPurse).getCurrentAmount(); - t.log('amt.value', invitationsAmt.value); - - const charterInvDetail = /** @type {SetValue} */ (invitationsAmt.value).find( - ({ description }) => description === INVITATION_MAKERS_DESC, - ); - t.assert(charterInvDetail); - - const charterInv = await E(billsInvitationPurse).withdraw( - AmountMath.make(invitationsAmt.brand, harden([charterInvDetail])), - ); - const charterInvitationMakers = await E.get( - E(E(zoe).offer(charterInv)).getOfferResult(), - ).invitationMakers; - - const params = { DebtLimit: AmountMath.make(stableBrand, 100n) }; - - // We happen to know how the timer is implemented. - /** @type { ERef } */ - const timer = /** @type {any } */ (s.space.consume.chainTimerService); - - const now = await E(timer).getCurrentTimestamp(); - const deadline = TimeMath.addAbsRel(now, 3n); - const startVotingSeat = E(zoe).offer( - await E(charterInvitationMakers).VoteOnParamChange(), - undefined, - undefined, - { - params, - instance: vaultsInstance, - deadline, - path: { paramPath: { key: { collateralBrand: brand } } }, - }, - ); - await E(startVotingSeat).getOfferResult(); - await E(startVotingSeat).getPayouts(); - - /** @type {ERef} */ - const committeePublic = E(zoe).getPublicFacet(economicCommittee); - const questions = await E(committeePublic).getOpenQuestions(); - t.log({ questions }); - t.true(questions.length > 0, 'question is open'); - const question = E(committeePublic).getQuestion(questions[0]); - const { positions, questionHandle, counterInstance } = await E( - question, - ).getDetails(); - - await Promise.all( - [...invitationPurses.values()].map(async p => { - const amt2 = await E(p).getCurrentAmount(); - - const item = /** @type {SetValue} */ (amt2.value).find( - ({ description }) => description.startsWith('Voter'), - ); - const inv = await E(p).withdraw( - AmountMath.make(amt2.brand, harden([item])), - ); - t.log({ inv }); - const seat = await E(zoe).offer(inv); - t.log({ seat }); - const { voter } = await E(seat).getOfferResult(); - t.log({ voter }); - return E(voter).castBallotFor(questionHandle, [positions[0]]); - }), - ); - - await E(timer).tick(); - await E(timer).tick(); - await E(timer).tick(); - - const count = E(zoe).getPublicFacet(counterInstance); - const outcome = await E(count).getOutcome(); - t.deepEqual(outcome, { - changes: { DebtLimit: { brand: stableBrand, value: 100n } }, - }); -}); - -// https://github.com/endojs/endo/issues/647 -// test.todo('users can open vaults'); diff --git a/packages/internal/src/action-types.js b/packages/internal/src/action-types.js index a7dec994dff..869f87c901e 100644 --- a/packages/internal/src/action-types.js +++ b/packages/internal/src/action-types.js @@ -2,6 +2,7 @@ export const AG_COSMOS_INIT = 'AG_COSMOS_INIT'; export const SWING_STORE_EXPORT = 'SWING_STORE_EXPORT'; +export const ENACTED_UPGRADE = 'ENACTED_UPGRADE'; export const BEGIN_BLOCK = 'BEGIN_BLOCK'; export const CALCULATE_FEES_IN_BEANS = 'CALCULATE_FEES_IN_BEANS'; export const CORE_EVAL = 'CORE_EVAL'; diff --git a/packages/internal/src/node/createBundles.js b/packages/internal/src/node/createBundles.js index d17d1bb45ea..ecc7d505b73 100644 --- a/packages/internal/src/node/createBundles.js +++ b/packages/internal/src/node/createBundles.js @@ -23,7 +23,7 @@ export const createBundlesFromAbsolute = async sourceBundles => { } const bundle = match[1]; - const args = cacheToArgs.get(cache) || ['--to', cache]; + const args = cacheToArgs.get(cache) || ['--cache-js', cache]; args.push(srcPath, bundle); cacheToArgs.set(cache, args); } diff --git a/packages/vats/scripts/build-game1-start.js b/packages/vats/scripts/build-game1-start.js index fbc64399d01..6b0dfb10917 100644 --- a/packages/vats/scripts/build-game1-start.js +++ b/packages/vats/scripts/build-game1-start.js @@ -27,7 +27,6 @@ export const game1ProposalBuilder = async ({ publishRef, install }) => { }); }; -/** @type {DeployScriptFunction} */ export default async (homeP, endowments) => { const { writeCoreProposal } = await makeHelpers(homeP, endowments); await writeCoreProposal('start-game1', game1ProposalBuilder); diff --git a/packages/vats/scripts/build-walletFactory-upgrade.js b/packages/vats/scripts/build-walletFactory-upgrade.js index c7cfcdcd93d..ca8d566c744 100644 --- a/packages/vats/scripts/build-walletFactory-upgrade.js +++ b/packages/vats/scripts/build-walletFactory-upgrade.js @@ -28,7 +28,6 @@ export const defaultProposalBuilder = async ({ publishRef, install }) => { }); }; -/** @type {DeployScriptFunction} */ export default async (homeP, endowments) => { const { writeCoreProposal } = await makeHelpers(homeP, endowments); await writeCoreProposal('upgrade-walletFactory', defaultProposalBuilder); diff --git a/packages/vats/scripts/set-core-proposal-env.js b/packages/vats/scripts/set-core-proposal-env.js index 5a8f800e4ed..45725cd233a 100755 --- a/packages/vats/scripts/set-core-proposal-env.js +++ b/packages/vats/scripts/set-core-proposal-env.js @@ -10,7 +10,7 @@ if (!spec) { } const vatConfigFile = require.resolve(spec); -const configJson = fs.readFileSync(vatConfigFile); +const configJson = fs.readFileSync(vatConfigFile, 'utf-8'); const config = JSON.parse(configJson); const envs = new Map();