diff --git a/packages/SwingSet/src/controller/initializeSwingset.js b/packages/SwingSet/src/controller/initializeSwingset.js index 90b8a23a66b..449b20bbeee 100644 --- a/packages/SwingSet/src/controller/initializeSwingset.js +++ b/packages/SwingSet/src/controller/initializeSwingset.js @@ -282,7 +282,8 @@ function sortObjectProperties(obj, firsts = []) { return result; } -/** @typedef {{ kernelBundles?: Record, verbose?: boolean, +/** + * @typedef {{ kernelBundles?: Record, verbose?: boolean, * addVatAdmin?: boolean, addComms?: boolean, addVattp?: boolean, * addTimer?: boolean, * }} InitializationOptions @@ -290,14 +291,14 @@ function sortObjectProperties(obj, firsts = []) { /** * @param {SwingSetConfig} config - * @param {string[]} argv + * @param {unknown} bootstrapArgs * @param {SwingStoreKernelStorage} kernelStorage * @param {InitializationOptions} initializationOptions * @param {{ env?: Record }} runtimeOptions */ export async function initializeSwingset( config, - argv = [], + bootstrapArgs, kernelStorage, initializationOptions = {}, runtimeOptions = {}, @@ -353,13 +354,13 @@ export async function initializeSwingset( kvStore.set('lockdownBundle', JSON.stringify(kernelBundles.lockdown)); kvStore.set('supervisorBundle', JSON.stringify(kernelBundles.supervisor)); - if (config.bootstrap && argv) { + if (config.bootstrap && bootstrapArgs) { const bootConfig = config.vats[config.bootstrap]; if (bootConfig) { if (!bootConfig.parameters) { bootConfig.parameters = {}; } - bootConfig.parameters.argv = argv; + bootConfig.parameters.argv = bootstrapArgs; } } diff --git a/packages/cosmic-swingset/src/chain-main.js b/packages/cosmic-swingset/src/chain-main.js index ca3ec1d570e..7aa21a7707b 100644 --- a/packages/cosmic-swingset/src/chain-main.js +++ b/packages/cosmic-swingset/src/chain-main.js @@ -302,7 +302,6 @@ export default async function main(progname, args, { env, homedir, agcc }) { }; const argv = { - ROLE: 'chain', bootMsg, }; const vatHref = await importMetaResolve( diff --git a/packages/cosmic-swingset/src/launch-chain.js b/packages/cosmic-swingset/src/launch-chain.js index bab473a6a97..7bf2b959a4d 100644 --- a/packages/cosmic-swingset/src/launch-chain.js +++ b/packages/cosmic-swingset/src/launch-chain.js @@ -53,8 +53,8 @@ const getHostKey = path => `host.${path}`; * @param {*} bridgeOutbound * @param {SwingStoreKernelStorage} kernelStorage * @param {string} vatconfig absolute path - * @param {Record} argv XXX argv should be an array but it's being called with object - * @param {{ ROLE: string }} env + * @param {unknown} bootstrapArgs JSON-serializable data + * @param {{}} env * @param {*} options */ export async function buildSwingset( @@ -62,14 +62,10 @@ export async function buildSwingset( bridgeOutbound, kernelStorage, vatconfig, - argv, + bootstrapArgs, env, { debugName = undefined, slogCallbacks, slogSender }, ) { - // FIXME: Find a better way to propagate the role. - process.env.ROLE = argv.ROLE; - env.ROLE = argv.ROLE; - const debugPrefix = debugName === undefined ? '' : `${debugName}:`; /** @type {import('@agoric/swingset-vat').SwingSetConfig | null} */ let config = await loadSwingsetConfigFile(vatconfig); @@ -122,8 +118,10 @@ export async function buildSwingset( } config.pinBootstrapRoot = true; - // @ts-expect-error XXX argv object - await initializeSwingset(config, argv, kernelStorage, { debugPrefix }); + await initializeSwingset(config, bootstrapArgs, kernelStorage, { + // @ts-expect-error debugPrefix? what's that? + debugPrefix, + }); } await ensureSwingsetInitialized(); const controller = await makeSwingsetController( @@ -275,7 +273,6 @@ export async function launch({ kernelStorage, vatconfig, argv, - // @ts-expect-error process.env default env, { debugName, diff --git a/packages/cosmic-swingset/src/sim-chain.js b/packages/cosmic-swingset/src/sim-chain.js index bfbe05ce81f..b817feb5c9a 100644 --- a/packages/cosmic-swingset/src/sim-chain.js +++ b/packages/cosmic-swingset/src/sim-chain.js @@ -58,7 +58,6 @@ export async function connectToFakeChain(basedir, GCI, delay, inbound) { const mailboxStorage = await makeMapStorage(mailboxFile); const argv = { - ROLE: 'sim-chain', giveMeAllTheAgoricPowers: true, hardcodedClientAddresses: [bootAddress], bootMsg: { diff --git a/packages/cosmic-swingset/src/sim-params.js b/packages/cosmic-swingset/src/sim-params.js index e4aacf5fe2f..e1e4d0d7b9c 100644 --- a/packages/cosmic-swingset/src/sim-params.js +++ b/packages/cosmic-swingset/src/sim-params.js @@ -58,8 +58,7 @@ export const defaultBeansPerUnit = [ makeStringBeans(BeansPerXsnapComputron, defaultBeansPerXsnapComputron), ]; -export const defaultBootstrapVatConfig = - '@agoric/vats/decentral-devnet-config.json'; +const defaultBootstrapVatConfig = '@agoric/vats/decentral-demo-config.json'; export const defaultPowerFlagFees = [ makePowerFlagFee('SMART_WALLET', [makeCoin('ubld', 10_000_000n)]), diff --git a/packages/inter-protocol/scripts/init-core.js b/packages/inter-protocol/scripts/init-core.js index 0574e6327e8..3b952711fdd 100644 --- a/packages/inter-protocol/scripts/init-core.js +++ b/packages/inter-protocol/scripts/init-core.js @@ -104,7 +104,7 @@ export const mainProposalBuilder = async ({ install: install0, wrapInstall, }) => { - const { ROLE = 'chain', VAULT_FACTORY_CONTROLLER_ADDR } = process.env; + const { VAULT_FACTORY_CONTROLLER_ADDR } = process.env; const install = wrapInstall ? wrapInstall(install0) : install0; @@ -119,7 +119,6 @@ export const mainProposalBuilder = async ({ getManifestCall: [ getManifestForMain.name, { - ROLE, vaultFactoryControllerAddress: VAULT_FACTORY_CONTROLLER_ADDR, installKeys: { ...publishGroup(installKeyGroups.main), @@ -139,7 +138,6 @@ export const defaultProposalBuilder = async ( /** @param {string|undefined} s */ const optBigInt = s => s && BigInt(s); const { - ROLE = env.ROLE || 'chain', vaultFactoryControllerAddress = env.VAULT_FACTORY_CONTROLLER_ADDR, minInitialPoolLiquidity = env.MIN_INITIAL_POOL_LIQUIDITY, endorsedUi, @@ -176,7 +174,6 @@ export const defaultProposalBuilder = async ( getManifestCall: [ getManifestForInterProtocol.name, { - ROLE, vaultFactoryControllerAddress, minInitialPoolLiquidity: optBigInt(minInitialPoolLiquidity), endorsedUi, diff --git a/packages/inter-protocol/src/proposals/core-proposal.js b/packages/inter-protocol/src/proposals/core-proposal.js index 842387ebd04..79a3729c552 100644 --- a/packages/inter-protocol/src/proposals/core-proposal.js +++ b/packages/inter-protocol/src/proposals/core-proposal.js @@ -3,15 +3,14 @@ import * as econBehaviors from './econ-behaviors.js'; import { ECON_COMMITTEE_MANIFEST } from './startEconCommittee.js'; export * from './econ-behaviors.js'; -export * from './sim-behaviors.js'; // @ts-expect-error Module './econ-behaviors.js' has already exported a member // named 'EconomyBootstrapPowers'. export * from './startPSM.js'; // eslint-disable-line import/export export * from './startEconCommittee.js'; // eslint-disable-line import/export -/** @type {import('@agoric/vats/src/core/manifest.js').BootstrapManifest} */ +/** @type {import('@agoric/vats/src/core/lib-boot.js').BootstrapManifest} */ const SHARED_MAIN_MANIFEST = harden({ - /** @type {import('@agoric/vats/src/core/manifest.js').BootstrapManifestPermit} */ + /** @type {import('@agoric/vats/src/core/lib-boot.js').BootstrapManifestPermit} */ [econBehaviors.startVaultFactory.name]: { consume: { board: 'board', @@ -200,19 +199,9 @@ export const getManifestForMain = ( }; }; -const roleToManifest = harden({ - chain: { - ...REWARD_MANIFEST, - ...STAKE_FACTORY_MANIFEST, - }, - 'sim-chain': SIM_CHAIN_MANIFEST, - client: {}, -}); - export const getManifestForInterProtocol = ( { restoreRef }, { - ROLE = 'chain', econCommitteeOptions, installKeys, vaultFactoryControllerAddress, @@ -237,7 +226,8 @@ export const getManifestForInterProtocol = ( manifest: { ...econCommitteeManifest.manifest, ...mainManifest.manifest, - ...roleToManifest[ROLE], + ...REWARD_MANIFEST, + ...STAKE_FACTORY_MANIFEST, }, installations: { ...econCommitteeManifest.installations, diff --git a/packages/inter-protocol/src/proposals/sim-behaviors.js b/packages/inter-protocol/src/proposals/sim-behaviors.js deleted file mode 100644 index 006d8ee0222..00000000000 --- a/packages/inter-protocol/src/proposals/sim-behaviors.js +++ /dev/null @@ -1,39 +0,0 @@ -import { E, Far } from '@endo/far'; -import { addRemote } from '@agoric/vats/src/core/utils.js'; - -export { connectFaucet } from './demoIssuers.js'; - -/** @param {BootstrapPowers} powers */ -export const installSimEgress = async ({ - vatParameters: { argv }, - vats: { vattp, comms }, - consume: { clientCreator }, -}) => { - const PROVISIONER_INDEX = 1; - - return Promise.all( - argv.hardcodedClientAddresses.map(async (addr, i) => { - const clientFacet = await E(clientCreator).createClientFacet( - `solo${i}`, - addr, - ['agoric.ALL_THE_POWERS'], - ); - - await addRemote(addr, { vats: { comms, vattp } }); - await E(comms).addEgress(addr, PROVISIONER_INDEX, clientFacet); - }), - ); -}; -harden(installSimEgress); - -/** @param {BootstrapPowers} powers */ -export const grantRunBehaviors = async ({ - runBehaviors, - consume: { client }, -}) => { - const bundle = { - behaviors: Far('behaviors', { run: manifest => runBehaviors(manifest) }), - }; - return E(client).assignBundle([_addr => bundle]); -}; -harden(grantRunBehaviors); diff --git a/packages/inter-protocol/src/proposals/startPSM.js b/packages/inter-protocol/src/proposals/startPSM.js index 6d2f8628409..10e3be72de0 100644 --- a/packages/inter-protocol/src/proposals/startPSM.js +++ b/packages/inter-protocol/src/proposals/startPSM.js @@ -15,6 +15,8 @@ import { inviteToEconCharter, } from './committee-proposal.js'; +/** @typedef {import('@agoric/vats/src/core/lib-boot.js').BootstrapManifest} BootstrapManifest */ + const BASIS_POINTS = 10000n; const { details: X } = assert; @@ -314,7 +316,7 @@ export const installGovAndPSMContracts = async ({ * named swingset bundles only in * decentral-psm-config.json * - * @type {import('@agoric/vats/src/core/manifest.js').BootstrapManifest} + * @type {BootstrapManifest} */ export const PSM_GOV_MANIFEST = { [installGovAndPSMContracts.name]: { @@ -345,8 +347,7 @@ export const PSM_GOV_MANIFEST = { }; export const INVITE_PSM_COMMITTEE_MANIFEST = harden( - /** @type {import('@agoric/vats/src/core/manifest.js').BootstrapManifest} */ - ({ + /** @type {BootstrapManifest} */ ({ [inviteCommitteeMembers.name]: { consume: { namesByAddressAdmin: true, @@ -363,9 +364,8 @@ export const INVITE_PSM_COMMITTEE_MANIFEST = harden( }), ); -/** @type {import('@agoric/vats/src/core/manifest.js').BootstrapManifest} */ -export const PSM_MANIFEST = harden({ - /** @type {import('@agoric/vats/src/core/manifest.js').BootstrapManifestPermit} */ +/** @type {BootstrapManifest} */ +export const PSM_MANIFEST = { [makeAnchorAsset.name]: { consume: { agoricNamesAdmin: true, bankManager: 'bank', zoe: 'zoe' }, installation: { consume: { mintHolder: 'zoe' } }, @@ -395,7 +395,8 @@ export const PSM_MANIFEST = harden({ consume: { [Stable.symbol]: 'zoe' }, }, }, -}); +}; +harden(PSM_MANIFEST); export const getManifestForPsmGovernance = ( { restoreRef }, diff --git a/packages/inter-protocol/test/psm/setupPsm.js b/packages/inter-protocol/test/psm/setupPsm.js index cd14ee081d7..8942b4e2b1f 100644 --- a/packages/inter-protocol/test/psm/setupPsm.js +++ b/packages/inter-protocol/test/psm/setupPsm.js @@ -1,10 +1,7 @@ import { Far, makeLoopback } from '@endo/captp'; import { E } from '@endo/eventual-send'; -import { - makeAgoricNamesAccess, - makePromiseSpace, -} from '@agoric/vats/src/core/utils.js'; +import { makeAgoricNamesAccess, makePromiseSpace } from '@agoric/vats'; import { makeBoard } from '@agoric/vats/src/lib-board.js'; import { Stable } from '@agoric/vats/src/tokens.js'; import { makeScalarMapStore } from '@agoric/vat-data'; diff --git a/packages/inter-protocol/test/reserve/setup.js b/packages/inter-protocol/test/reserve/setup.js index b90f0d36101..b6b5b941772 100644 --- a/packages/inter-protocol/test/reserve/setup.js +++ b/packages/inter-protocol/test/reserve/setup.js @@ -1,9 +1,6 @@ import buildManualTimer from '@agoric/zoe/tools/manualTimer.js'; import { E } from '@endo/eventual-send'; -import { - makeAgoricNamesAccess, - makePromiseSpace, -} from '@agoric/vats/src/core/utils.js'; +import { makeAgoricNamesAccess, makePromiseSpace } from '@agoric/vats'; import { makeBoard } from '@agoric/vats/src/lib-board.js'; import { setupReserve } from '../../src/proposals/econ-behaviors.js'; diff --git a/packages/inter-protocol/test/stakeFactory/test-stakeFactory.js b/packages/inter-protocol/test/stakeFactory/test-stakeFactory.js index 86431bb6e2d..dc66880b8d8 100644 --- a/packages/inter-protocol/test/stakeFactory/test-stakeFactory.js +++ b/packages/inter-protocol/test/stakeFactory/test-stakeFactory.js @@ -9,10 +9,7 @@ import { makeCopyBag } from '@agoric/store'; import { unsafeMakeBundleCache } from '@agoric/swingset-vat/tools/bundleTool.js'; import centralSupplyBundle from '@agoric/vats/bundles/bundle-centralSupply.js'; import mintHolderBundle from '@agoric/vats/bundles/bundle-mintHolder.js'; -import { - makeAgoricNamesAccess, - makePromiseSpace, -} from '@agoric/vats/src/core/utils.js'; +import { makeAgoricNamesAccess, makePromiseSpace } from '@agoric/vats'; import { makeBoard } from '@agoric/vats/src/lib-board.js'; import { Stable } from '@agoric/vats/src/tokens.js'; import { makeRatio } from '@agoric/zoe/src/contractSupport/index.js'; diff --git a/packages/inter-protocol/test/supports.js b/packages/inter-protocol/test/supports.js index b9ab325420a..e9d2479c851 100644 --- a/packages/inter-protocol/test/supports.js +++ b/packages/inter-protocol/test/supports.js @@ -4,10 +4,7 @@ import committeeBundle from '@agoric/governance/bundles/bundle-committee.js'; import contractGovernorBundle from '@agoric/governance/bundles/bundle-contractGovernor.js'; import puppetContractGovernorBundle from '@agoric/governance/bundles/bundle-puppetContractGovernor.js'; import * as utils from '@agoric/vats/src/core/utils.js'; -import { - makeAgoricNamesAccess, - makePromiseSpace, -} from '@agoric/vats/src/core/utils.js'; +import { makePromiseSpace, makeAgoricNamesAccess } from '@agoric/vats'; import { makeBoard } from '@agoric/vats/src/lib-board.js'; import { Stable } from '@agoric/vats/src/tokens.js'; import { makeMockChainStorageRoot } from '@agoric/internal/src/storage-test-utils.js'; diff --git a/packages/internal/src/magic-cookie-test-only.js b/packages/internal/src/magic-cookie-test-only.js new file mode 100644 index 00000000000..df4e788e8db --- /dev/null +++ b/packages/internal/src/magic-cookie-test-only.js @@ -0,0 +1,9 @@ +const cookie = harden({}); + +/** + * Facilitate static analysis to prevent + * demo/test facilities from being bundled in production. + */ +export const notForProductionUse = () => { + return cookie; +}; diff --git a/packages/smart-wallet/src/walletFactory.js b/packages/smart-wallet/src/walletFactory.js index ea2b6197ae7..7e5edcb0778 100644 --- a/packages/smart-wallet/src/walletFactory.js +++ b/packages/smart-wallet/src/walletFactory.js @@ -9,7 +9,7 @@ import { observeIteration } from '@agoric/notifier'; import { M, makeExo, makeScalarMapStore, mustMatch } from '@agoric/store'; import { makeAtomicProvider } from '@agoric/store/src/stores/store-utils.js'; import { prepareExo, provideDurableMapStore } from '@agoric/vat-data'; -import { makeMyAddressNameAdminKit } from '@agoric/vats/src/core/basic-behaviors.js'; +import { makeMyAddressNameAdminKit } from '@agoric/vats/src/core/utils.js'; import { provideAll } from '@agoric/zoe/src/contractSupport/durability.js'; import { E } from '@endo/far'; import { prepareSmartWallet } from './smartWallet.js'; diff --git a/packages/smart-wallet/test/supports.js b/packages/smart-wallet/test/supports.js index 170318dfede..4a4487d43b7 100644 --- a/packages/smart-wallet/test/supports.js +++ b/packages/smart-wallet/test/supports.js @@ -8,10 +8,7 @@ import { } from '@agoric/vats/src/core/basic-behaviors.js'; import { setupClientManager } from '@agoric/vats/src/core/chain-behaviors.js'; import '@agoric/vats/src/core/types.js'; -import { - makeAgoricNamesAccess, - makePromiseSpace, -} from '@agoric/vats/src/core/utils.js'; +import { makeAgoricNamesAccess, makePromiseSpace } from '@agoric/vats'; import { buildRootObject as boardRoot } from '@agoric/vats/src/vat-board.js'; import { buildRootObject as mintsRoot } from '@agoric/vats/src/vat-mints.js'; import { makeMockChainStorageRoot } from '@agoric/internal/src/storage-test-utils.js'; diff --git a/packages/solo/solo-config.json b/packages/solo/solo-config.json index 75e1e4845c0..d215366ff32 100644 --- a/packages/solo/solo-config.json +++ b/packages/solo/solo-config.json @@ -16,7 +16,7 @@ "sourceSpec": "./src/vat-uploads.js" }, "bootstrap": { - "sourceSpec": "@agoric/vats/src/core/boot.js" + "sourceSpec": "@agoric/vats/src/core/boot-solo.js" } } } diff --git a/packages/solo/test/test-home.js b/packages/solo/test/test-home.js index 35ad5f7b693..4a93cfc8578 100644 --- a/packages/solo/test/test-home.js +++ b/packages/solo/test/test-home.js @@ -2,7 +2,7 @@ import { test } from '@agoric/swingset-vat/tools/prepare-test-env-ava.js'; -import bundleSource from '@endo/bundle-source'; +import bundleSourceAmbient from '@endo/bundle-source'; import { AmountMath } from '@agoric/ertp'; import { Stable } from '@agoric/vats/src/tokens.js'; import { Far } from '@endo/marshal'; @@ -12,20 +12,29 @@ import { makeFixture, E } from './captp-fixture.js'; const SOLO_PORT = 7999; -// This runs before all the tests. -let home; -let teardown; +// #region setup (ambient authority is confined to this region) test.before('setup', async t => { + const loadBundle = async specifier => { + const contractUrl = await importMetaResolve(specifier, import.meta.url); + const contractRoot = new URL(contractUrl).pathname; + t.log({ contractRoot }); + const bundle = await bundleSourceAmbient(contractRoot); + return bundle; + }; const { homeP, kill } = await makeFixture(SOLO_PORT, process.env.NOISY); - teardown = kill; - home = await homeP; + const home = await homeP; + + t.context = { home, teardown: kill, loadBundle }; + t.truthy('ready'); }); +// #endregion // Now come the tests that use `home`... // ========================================= test.serial('home.board', async t => { + const { home } = t.context; const { board } = E.get(home); await t.throwsAsync( () => E(board).getValue('board0120'), @@ -50,6 +59,7 @@ test.serial('home.board', async t => { }); test.serial('home.wallet - transfer funds to the feePurse', async t => { + const { home } = t.context; const { wallet, faucet } = E.get(home); const feePurse = E(faucet).getFeePurse(); const feeBrand = await E(feePurse).getAllegedBrand(); @@ -62,16 +72,13 @@ test.serial('home.wallet - transfer funds to the feePurse', async t => { }); test.serial('home.wallet - receive zoe invite', async t => { + const { home, loadBundle } = t.context; const { wallet, zoe, board } = E.get(home); // Setup contract in order to get an invite to use in tests - const contractUrl = await importMetaResolve( + const bundle = await loadBundle( '@agoric/zoe/src/contracts/automaticRefund.js', - import.meta.url, ); - const contractRoot = new URL(contractUrl).pathname; - t.log({ contractRoot }); - const bundle = await bundleSource(contractRoot); const installationHandle = await E(zoe).install(bundle); const { creatorInvitation: invite } = await E(zoe).startInstance( installationHandle, @@ -113,6 +120,7 @@ test.serial('home.wallet - receive zoe invite', async t => { }); test.serial('home.wallet - central issuer setup', async t => { + const { home } = t.context; const { wallet } = E.get(home); // Check that the wallet knows about the central issuer. @@ -127,6 +135,7 @@ test.serial('home.wallet - central issuer setup', async t => { }); test.serial('home.localTimerService makeNotifier', async t => { + const { home } = t.context; const { localTimerService } = E.get(home); const notifier = E(localTimerService).makeNotifier(1n, 1n); const update1 = await E(notifier).getUpdateSince(); @@ -158,6 +167,7 @@ function makeHandler() { } test.serial('home.localTimerService makeRepeater', async t => { + const { home } = t.context; const { localTimerService } = E.get(home); const timestamp = await E(localTimerService).getCurrentTimestamp(); const repeater = E(localTimerService).makeRepeater(1n, 1n); @@ -173,6 +183,7 @@ test.serial('home.localTimerService makeRepeater', async t => { // ========================================= // This runs after all the tests. test.after.always('teardown', async t => { + const { teardown } = t.context; await teardown(); t.truthy('shutdown'); }); diff --git a/packages/vats/decentral-core-config.json b/packages/vats/decentral-core-config.json index f106850083a..d715e089104 100644 --- a/packages/vats/decentral-core-config.json +++ b/packages/vats/decentral-core-config.json @@ -1,9 +1,10 @@ { + "$comment": "This SwingSet config file (see loadSwingsetConfigFile) is minimal; it has no proposals. It is used by ../cosmic-swingset/Makefile and `agoric start`, which add proposals to it.", "bootstrap": "bootstrap", "defaultReapInterval": 1000, "vats": { "bootstrap": { - "sourceSpec": "@agoric/vats/src/core/boot.js", + "sourceSpec": "@agoric/vats/src/core/boot-chain.js", "creationOptions": { "critical": true } diff --git a/packages/vats/decentral-demo-config.json b/packages/vats/decentral-demo-config.json index 42cd1cbe202..ddd19622a8f 100644 --- a/packages/vats/decentral-demo-config.json +++ b/packages/vats/decentral-demo-config.json @@ -1,39 +1,129 @@ { + "$comment": "This SwingSet config file (see loadSwingsetConfigFile) includes non-production facilities such as a faucet. It is used by the sim chain (agoric start dev) and testnet-load-generator (aka loadgen).", "bootstrap": "bootstrap", "defaultReapInterval": 1000, + "snapshotInterval": 1000, "coreProposals": [ - "@agoric/inter-protocol/scripts/init-core.js", - "@agoric/pegasus/scripts/init-core.js" + "@agoric/vats/scripts/init-core.js", + { + "module": "@agoric/inter-protocol/scripts/init-core.js", + "entrypoint": "defaultProposalBuilder", + "args": [ + { + "econCommitteeOptions": { + "committeeSize": 3 + } + } + ] + }, + { + "module": "@agoric/inter-protocol/scripts/add-collateral-core.js", + "entrypoint": "psmGovernanceBuilder", + "args": [] + }, + { + "module": "@agoric/inter-protocol/scripts/add-collateral-core.js", + "entrypoint": "defaultProposalBuilder", + "args": [ + { + "interchainAssetOptions": { + "denom": "ibc/toyatom", + "decimalPlaces": 6, + "initialPrice": 12.34, + "keyword": "IbcATOM", + "oracleBrand": "ATOM", + "proposedName": "ATOM" + } + } + ] + }, + { + "module": "@agoric/inter-protocol/scripts/add-collateral-core.js", + "entrypoint": "psmProposalBuilder", + "args": [ + { + "anchorOptions": { + "denom": "ibc/toyusdc", + "decimalPlaces": 6, + "keyword": "USDC_axl", + "proposedName": "USD Coin" + } + } + ] + }, + { + "module": "@agoric/inter-protocol/scripts/add-collateral-core.js", + "entrypoint": "psmProposalBuilder", + "args": [ + { + "anchorOptions": { + "denom": "ibc/toyellie", + "decimalPlaces": 6, + "keyword": "AUSD", + "proposedName": "Anchor USD" + } + } + ] + }, + { + "module": "@agoric/inter-protocol/scripts/price-feed-core.js", + "entrypoint": "defaultProposalBuilder", + "args": [ + { + "AGORIC_INSTANCE_NAME": "ATOM-USD price feed", + "oracleAddresses": [ + "agoric1ldmtatp24qlllgxmrsjzcpe20fvlkp448zcuce", + "agoric140dmkrz2e42ergjj7gyvejhzmjzurvqeq82ang" + ], + "IN_BRAND_LOOKUP": [ + "agoricNames", + "oracleBrand", + "ATOM" + ], + "IN_BRAND_DECIMALS": 6, + "OUT_BRAND_LOOKUP": [ + "agoricNames", + "oracleBrand", + "USD" + ], + "OUT_BRAND_DECIMALS": 4 + } + ] + }, + { + "module": "@agoric/inter-protocol/scripts/invite-committee-core.js", + "entrypoint": "defaultProposalBuilder", + "args": [ + { + "voterAddresses": { + "gov1": "agoric1ldmtatp24qlllgxmrsjzcpe20fvlkp448zcuce", + "gov2": "agoric140dmkrz2e42ergjj7gyvejhzmjzurvqeq82ang", + "gov3": "agoric1w8wktaur4zf8qmmtn3n7x3r0jhsjkjntcm3u6h" + } + } + ] + } ], "vats": { "bootstrap": { - "sourceSpec": "@agoric/vats/src/core/boot.js", - "parameters": { - "governanceActions": true, - "economicCommitteeAddresses": { - "voter": "agoric1Ersatz" - } - }, + "sourceSpec": "@agoric/vats/src/core/boot-sim.js", "creationOptions": { "critical": true } } }, "bundles": { + "bank": { + "sourceSpec": "@agoric/vats/src/vat-bank.js" + }, "centralSupply": { "sourceSpec": "@agoric/vats/src/centralSupply.js" }, - "chainStorage": { - "sourceSpec": "@agoric/vats/src/vat-chainStorage.js" - }, "mintHolder": { "sourceSpec": "@agoric/vats/src/mintHolder.js" }, - "zcf": { - "sourceSpec": "@agoric/zoe/contractFacet.js" - }, - "bank": { - "sourceSpec": "@agoric/vats/src/vat-bank.js" + "mints": { + "sourceSpec": "@agoric/vats/src/vat-mints.js" }, "board": { "sourceSpec": "@agoric/vats/src/vat-board.js" @@ -41,9 +131,6 @@ "ibc": { "sourceSpec": "@agoric/vats/src/vat-ibc.js" }, - "mints": { - "sourceSpec": "@agoric/vats/src/vat-mints.js" - }, "network": { "sourceSpec": "@agoric/vats/src/vat-network.js" }, @@ -53,18 +140,15 @@ "provisioning": { "sourceSpec": "@agoric/vats/src/vat-provisioning.js" }, - "provisionPool": { - "sourceSpec": "@agoric/vats/src/provisionPool.js" - }, - "sharing": { - "sourceSpec": "@agoric/vats/src/vat-sharing.js" + "chainStorage": { + "sourceSpec": "@agoric/vats/src/vat-chainStorage.js" }, - "walletFactory": { - "sourceSpec": "@agoric/smart-wallet/src/walletFactory.js" + "zcf": { + "sourceSpec": "@agoric/zoe/contractFacet.js" }, "zoe": { "sourceSpec": "@agoric/vats/src/vat-zoe.js" } }, "defaultManagerType": "xs-worker" -} +} \ No newline at end of file diff --git a/packages/vats/decentral-devnet-config.json b/packages/vats/decentral-devnet-config.json index fe001159c19..a9072c8f998 100644 --- a/packages/vats/decentral-devnet-config.json +++ b/packages/vats/decentral-devnet-config.json @@ -1,4 +1,5 @@ { + "$comment": "This SwingSet config file (see loadSwingsetConfigFile) includes non-production facilities such as a faucet. It does not include vaults in coreProposals; vaults are expected to be added by devnet governance.", "bootstrap": "bootstrap", "defaultReapInterval": 1000, "snapshotInterval": 1000, @@ -106,7 +107,7 @@ ], "vats": { "bootstrap": { - "sourceSpec": "@agoric/vats/src/core/boot.js", + "sourceSpec": "@agoric/vats/src/core/boot-chain.js", "creationOptions": { "critical": true } diff --git a/packages/vats/decentral-main-psm-config.json b/packages/vats/decentral-main-psm-config.json index fb4e3680a0a..d67ff2f21e9 100644 --- a/packages/vats/decentral-main-psm-config.json +++ b/packages/vats/decentral-main-psm-config.json @@ -1,4 +1,5 @@ { + "$comment": "This config is obsolete. TODO: prune it", "bootstrap": "bootstrap", "defaultReapInterval": 1000, "snapshotInterval": 1000, diff --git a/packages/vats/decentral-psm-config.json b/packages/vats/decentral-psm-config.json index 526148c58e4..d6da255491b 100644 --- a/packages/vats/decentral-psm-config.json +++ b/packages/vats/decentral-psm-config.json @@ -1,4 +1,5 @@ { + "$comment": "This config is obsolete. TODO: prune it", "bootstrap": "bootstrap", "defaultReapInterval": 1000, "snapshotInterval": 1000, diff --git a/packages/vats/decentral-test-psm-config.json b/packages/vats/decentral-test-psm-config.json index 03a13f05eed..e051419a236 100644 --- a/packages/vats/decentral-test-psm-config.json +++ b/packages/vats/decentral-test-psm-config.json @@ -1,4 +1,5 @@ { + "$comment": "This config is obsolete. TODO: prune it", "bootstrap": "bootstrap", "defaultReapInterval": 1000, "snapshotInterval": 1000, diff --git a/packages/vats/decentral-test-vaults-config.json b/packages/vats/decentral-test-vaults-config.json index 186a66518ac..799bd7427a2 100644 --- a/packages/vats/decentral-test-vaults-config.json +++ b/packages/vats/decentral-test-vaults-config.json @@ -1,4 +1,5 @@ { + "$comment": "This SwingSet config file (see loadSwingsetConfigFile) is designed to bring up vaults test networks in an automated fashion. It includes coreProposals to start vaults. Testing facilities are limited to an initialPrice for ATOM and addresses with known keys.", "bootstrap": "bootstrap", "defaultReapInterval": 1000, "coreProposals": [ @@ -16,7 +17,6 @@ } ] }, - "@agoric/pegasus/scripts/init-core.js", { "module": "@agoric/inter-protocol/scripts/add-collateral-core.js", "entrypoint": "psmGovernanceBuilder", @@ -149,7 +149,7 @@ ], "vats": { "bootstrap": { - "sourceSpec": "@agoric/vats/src/core/boot.js", + "sourceSpec": "@agoric/vats/src/core/boot-chain.js", "creationOptions": { "critical": true } @@ -177,9 +177,6 @@ "ibc": { "sourceSpec": "@agoric/vats/src/vat-ibc.js" }, - "mints": { - "sourceSpec": "@agoric/vats/src/vat-mints.js" - }, "network": { "sourceSpec": "@agoric/vats/src/vat-network.js" }, @@ -192,9 +189,6 @@ "provisioning": { "sourceSpec": "@agoric/vats/src/vat-provisioning.js" }, - "sharing": { - "sourceSpec": "@agoric/vats/src/vat-sharing.js" - }, "walletFactory": { "sourceSpec": "@agoric/smart-wallet/src/walletFactory.js" }, diff --git a/packages/vats/index.js b/packages/vats/index.js index d6eaf085e1a..05aabbc4f3b 100644 --- a/packages/vats/index.js +++ b/packages/vats/index.js @@ -2,8 +2,9 @@ // Ambient types import './src/core/types.js'; -export * from './src/core/boot.js'; // eslint-disable-next-line import/export -- no named exports export * from './src/types.js'; export * from './src/nameHub.js'; export * from './src/bridge.js'; +export { makePromiseSpace } from './src/core/promise-space.js'; +export { makeAgoricNamesAccess } from './src/core/utils.js'; diff --git a/packages/vats/src/authorityViz.js b/packages/vats/src/authorityViz.js deleted file mode 100644 index 07d4db488c8..00000000000 --- a/packages/vats/src/authorityViz.js +++ /dev/null @@ -1,203 +0,0 @@ -// @ts-check -import '@endo/init'; -import process from 'process'; - -import * as manifests from './core/manifest.js'; - -const { entries } = Object; - -const styles = { - vatPowers: 'shape=star, style=filled, fillcolor=aqua', - vats: 'shape=doubleoctagon, style=filled, fillcolor=tomato', - vat: 'shape=doubleoctagon, style=filled, fillcolor=tomato3', - devices: 'shape=box, style=filled, fillcolor=gold', - space: 'shape=house, style=filled, fillcolor=khaki', - issuer: 'shape=trapezium, style=filled, fillcolor=chocolate', - brand: 'shape=Mcircle, style=filled, fillcolor=chocolate2', - installation: 'shape=cylinder', - instance: 'shape=component', - home: 'shape=folder', -}; - -/** - * @param {Set} nodes - * @param {Map>} neighbors - * @yields { string } - * @typedef {{ - * id: string, - * cluster?: string, - * label: string, - * style?: string, - * }} GraphNode - */ -function* fmtGraph(nodes, neighbors) { - const q = txt => JSON.stringify(txt.replace(/\./g, '_')); - yield 'digraph G {\n'; - yield 'rankdir = LR;\n'; - const clusters = new Set( - [...nodes].map(({ cluster }) => cluster).filter(c => !!c), - ); - for (const subgraph of [...clusters, undefined]) { - if (subgraph) { - assert.typeof(subgraph, 'string'); - yield `subgraph cluster_${subgraph} {\n`; - yield `label = "${subgraph}"\n`; - } - for (const { id, cluster, label, style } of nodes) { - if (subgraph && cluster !== subgraph) continue; - yield `${q(id)} [label=${q(label)}${style ? `, ${style}` : ''}];\n`; - } - if (subgraph) { - yield `}\n`; - } - } - for (const [src, arcs] of neighbors.entries()) { - for (const { id, style } of arcs) { - yield `${q(src)} -> ${q(id)} [${style}]\n`; - } - } - yield '}\n'; -} - -/** - * - * @param {Record} manifest - * @typedef {{ - * vatParameters?: Record, - * vatPowers?: Record - * vats?: Record - * devices?: Record - * home?: PowerSpace, - * issuer?: PowerSpace, - * brand?: PowerSpace, - * installation?: PowerSpace, - * instance?: PowerSpace, - * runBehaviors: Function, - * } & PowerSpace } Permit - * @typedef {{produce?: Record, consume?: Record}} PowerSpace - * @typedef { boolean | VatName } Status - * @typedef { string } VatName - */ -const manifest2graph = manifest => { - /** @type { Set } */ - const nodes = new Set(); - const neighbors = new Map(); - - /** - * @param {string} src - * @param {string} dest - * @param {string} [style] - */ - const addEdge = (src, dest, style = '') => { - if (!neighbors.has(src)) { - neighbors.set(src, new Set()); - } - neighbors.get(src).add({ id: dest, style }); - }; - - /** - * @param {string} src - * @param {string} ty - * @param {Record | undefined} item - * @param {boolean} [reverse=false] - */ - const level1 = (src, ty, item, reverse = false) => { - if (item) { - for (const [powerName, status] of entries(item)) { - // subsumed by permits for vat:, home:, ... - if ( - [ - 'loadVat', - 'loadCriticalVat', - 'client', - 'agoricNames', - 'nameHubs', - 'nameAdmins', - ].includes(powerName) && - reverse - ) - continue; - if (status) { - let cluster = {}; - if (typeof status !== 'boolean') { - cluster = { cluster: status }; - } - - nodes.add({ - id: `${ty}.${powerName}`, - label: powerName, - style: styles[ty], - ...cluster, - }); - addEdge(src, `${ty}.${powerName}`, reverse ? 'dir=back' : ''); - if (ty === 'home') { - nodes.add({ id: 'home', label: 'home', cluster: 'provisioning' }); - addEdge('home', `${ty}.${powerName}`); - } - } - } - } - }; - - for (const [fnName, permit] of entries(manifest)) { - nodes.add({ id: fnName, label: fnName }); - - level1(fnName, 'vatPowers', permit.vatPowers, true); - level1(fnName, 'vats', permit.vats, true); - level1(fnName, 'devices', permit.devices, true); - level1(fnName, 'space', permit.produce); - level1(fnName, 'space', permit.consume, true); - level1(fnName, 'home', (permit.home || {}).produce); - level1(fnName, 'issuer', (permit.issuer || {}).produce); - level1(fnName, 'issuer', (permit.issuer || {}).consume, true); - level1(fnName, 'brand', (permit.brand || {}).produce); - level1(fnName, 'brand', (permit.brand || {}).consume, true); - level1(fnName, 'installation', (permit.installation || {}).produce); - level1(fnName, 'installation', (permit.installation || {}).consume, true); - level1(fnName, 'instance', (permit.instance || {}).produce); - level1(fnName, 'instance', (permit.instance || {}).consume, true); - if (permit.runBehaviors) { - nodes.add({ - id: `runBehaviors`, - label: `runBehaviors`, - style: '', - }); - addEdge(fnName, `runBehaviors`); - } - } - return { nodes, neighbors }; -}; - -/** - * @param {string[]} args - * @param {object} io - * @param {typeof import('process').stdout} io.stdout - */ -const main = async (args, { stdout }) => { - const [...opts] = args; - // if (!fn) throw Error('usage: authorityViz [--sim-chain]'); - const [sim, gov] = ['--sim-chain', '--gov'].map(opt => opts.includes(opt)); - const manifest = sim - ? manifests.SIM_CHAIN_BOOTSTRAP_MANIFEST - : manifests.CHAIN_BOOTSTRAP_MANIFEST; - if (gov) { - /* - const postBoot = sim - ? manifests.SIM_CHAIN_POST_BOOT_MANIFEST - : manifests.CHAIN_POST_BOOT_MANIFEST; - manifest = { ...postBoot, ...manifest }; - */ - } - - // console.log(JSON.stringify(bootstrapManifest, null, 2)); - const g = manifest2graph(/** @type {any} */ (manifest)); - for (const chunk of fmtGraph(g.nodes, g.neighbors)) { - stdout.write(chunk); - } -}; - -(async () => { - return main(process.argv.slice(2), { - stdout: process.stdout, - }); -})().catch(console.error); diff --git a/packages/vats/src/core/basic-behaviors.js b/packages/vats/src/core/basic-behaviors.js index f4b2ddc3d0c..2b27c2592fb 100644 --- a/packages/vats/src/core/basic-behaviors.js +++ b/packages/vats/src/core/basic-behaviors.js @@ -1,30 +1,18 @@ // @ts-check -import { AssetKind, makeIssuerKit } from '@agoric/ertp'; import { Nat } from '@endo/nat'; +import { E, Far } from '@endo/far'; +import { AssetKind, makeIssuerKit } from '@agoric/ertp'; import { makeScalarMapStore } from '@agoric/store'; import { provideLazy } from '@agoric/store/src/stores/store-utils.js'; -import { E, Far } from '@endo/far'; import { BridgeId, VBankAccount, WalletName } from '@agoric/internal'; import { makeNameHubKit } from '../nameHub.js'; -import { feeIssuerConfig } from './utils.js'; +import { feeIssuerConfig, makeMyAddressNameAdminKit } from './utils.js'; import { Stable, Stake } from '../tokens.js'; +import { PowerFlags } from '../walletFlags.js'; const { details: X } = assert; -// XXX domain of @agoric/cosmic-proto -/** - * non-exhaustive list of powerFlags - * REMOTE_WALLET is currently a default. - * - * See also MsgProvision in golang/cosmos/proto/agoric/swingset/msgs.proto - */ -export const PowerFlags = /** @type {const} */ ({ - SMART_WALLET: 'SMART_WALLET', - /** The ag-solo wallet is remote. */ - REMOTE_WALLET: 'REMOTE_WALLET', -}); - /** * In golang/cosmos/app/app.go, we define * cosmosInitAction with type AG_COSMOS_INIT, @@ -124,9 +112,7 @@ export const buildZoe = async ({ invitationBrand.resolve(brand); feeMintAccess.resolve(fma); - return Promise.all([ - E(client).assignBundle([_addr => ({ zoe: zoeService })]), - ]); + await Promise.all([E(client).assignBundle([_addr => ({ zoe: zoeService })])]); }; harden(buildZoe); @@ -191,24 +177,6 @@ export const makeBoard = async ({ }; harden(makeBoard); -/** - * @param {string} address - */ -export const makeMyAddressNameAdminKit = address => { - // Create a name hub for this address. - const { nameHub, nameAdmin: rawMyAddressNameAdmin } = makeNameHubKit(); - - /** @type {import('../types').MyAddressNameAdmin} */ - const myAddressNameAdmin = Far('myAddressNameAdmin', { - ...rawMyAddressNameAdmin, - getMyAddress: () => address, - }); - // reserve space for deposit facet - myAddressNameAdmin.reserve(WalletName.depositFacet); - - return { nameHub, myAddressNameAdmin }; -}; - /** * Make the agoricNames, namesByAddress name hierarchies. * @@ -422,7 +390,7 @@ export const addBankAssets = async ({ produceIssuer.IST.resolve(runKit.issuer); produceBrand.BLD.resolve(bldKit.brand); produceBrand.IST.resolve(runKit.brand); - return Promise.all([ + await Promise.all([ E(bankMgr).addAsset( Stake.denom, Stake.symbol, @@ -438,3 +406,126 @@ export const addBankAssets = async ({ ]); }; harden(addBankAssets); + +/** @type {import('./lib-boot').BootstrapManifest} */ +export const BASIC_BOOTSTRAP_PERMITS = { + bridgeCoreEval: true, // Needs all the powers. + [makeOracleBrands.name]: { + oracleBrand: { + produce: { + USD: true, + }, + }, + }, + [startPriceAuthority.name]: { + consume: { loadCriticalVat: true, client: true }, + produce: { + priceAuthorityVat: 'priceAuthority', + priceAuthority: 'priceAuthority', + priceAuthorityAdmin: 'priceAuthority', + }, + }, + [makeVatsFromBundles.name]: { + vats: { + vatAdmin: 'vatAdmin', + }, + devices: { + vatAdmin: 'kernel', + }, + produce: { + vatAdminSvc: 'vatAdmin', + loadVat: true, + loadCriticalVat: true, + vatStore: true, + }, + }, + [buildZoe.name]: { + consume: { + vatAdminSvc: true, + loadCriticalVat: true, + client: true, + }, + produce: { + zoe: 'zoe', + feeMintAccess: 'zoe', + }, + issuer: { produce: { Invitation: 'zoe' } }, + brand: { produce: { Invitation: 'zoe' } }, + }, + [makeBoard.name]: { + consume: { + loadCriticalVat: true, + client: true, + }, + produce: { + board: 'board', + }, + }, + + [makeAddressNameHubs.name]: { + consume: { + agoricNames: true, + client: true, + }, + produce: { + namesByAddress: true, + namesByAddressAdmin: true, + }, + home: { + produce: { myAddressNameAdmin: true }, + }, + }, + [makeClientBanks.name]: { + consume: { + namesByAddressAdmin: true, + bankManager: 'bank', + client: true, + walletFactoryStartResult: 'walletFactory', + }, + home: { produce: { bank: 'bank' } }, + }, + [installBootContracts.name]: { + consume: { zoe: 'zoe', vatAdminSvc: true }, + installation: { + produce: { + centralSupply: 'zoe', + mintHolder: 'zoe', + }, + }, + }, + [mintInitialSupply.name]: { + vatParameters: { + argv: { bootMsg: true }, + }, + consume: { + feeMintAccess: true, + zoe: true, + }, + produce: { + initialSupply: true, + }, + installation: { + consume: { centralSupply: 'zoe' }, + }, + }, + [addBankAssets.name]: { + consume: { + agoricNamesAdmin: true, + initialSupply: true, + bridgeManager: 'chainStorage', + // TODO: re-org loadCriticalVat to be subject to permits + loadCriticalVat: true, + zoe: true, + }, + produce: { + bankManager: 'bank', + bldIssuerKit: true, + }, + installation: { + consume: { centralSupply: 'zoe', mintHolder: 'zoe' }, + }, + issuer: { produce: { BLD: 'BLD', IST: 'zoe' } }, + brand: { produce: { BLD: 'BLD', IST: 'zoe' } }, + }, +}; +harden(BASIC_BOOTSTRAP_PERMITS); diff --git a/packages/vats/src/core/behaviors.js b/packages/vats/src/core/behaviors.js deleted file mode 100644 index ed8f3f01a32..00000000000 --- a/packages/vats/src/core/behaviors.js +++ /dev/null @@ -1,4 +0,0 @@ -// @ts-check -export * from './basic-behaviors.js'; -export * from './chain-behaviors.js'; -// We exclude sim-behaviors.js from this list because it should not be used in production. diff --git a/packages/vats/src/core/boot-chain.js b/packages/vats/src/core/boot-chain.js new file mode 100644 index 00000000000..f0d55bb5b8d --- /dev/null +++ b/packages/vats/src/core/boot-chain.js @@ -0,0 +1,40 @@ +// @ts-check +import { makeBootstrap } from './lib-boot.js'; + +import * as basicBehaviorsPlus from './basic-behaviors.js'; +import * as chainBehaviorsPlus from './chain-behaviors.js'; +import * as utils from './utils.js'; + +const { BASIC_BOOTSTRAP_PERMITS: _b, ...basicBehaviors } = basicBehaviorsPlus; +const { + CHAIN_BOOTSTRAP_MANIFEST, + SHARED_CHAIN_BOOTSTRAP_MANIFEST: _s, + ...chainBehaviors +} = chainBehaviorsPlus; +const behaviors = { ...basicBehaviors, ...chainBehaviors }; + +const modules = { + behaviors: { ...behaviors }, + utils: { ...utils }, +}; + +export const MANIFEST = CHAIN_BOOTSTRAP_MANIFEST; + +/** + * Build root object of the bootstrap vat. + * + * @param {{ + * D: DProxy, + * logger: (msg) => void, + * }} vatPowers + * @param {{ + * coreProposalCode?: string, + * }} vatParameters + */ +export const buildRootObject = (vatPowers, vatParameters) => { + console.debug(`chain bootstrap starting`); + + return makeBootstrap(vatPowers, vatParameters, MANIFEST, behaviors, modules); +}; + +harden({ buildRootObject }); diff --git a/packages/vats/src/core/boot-psm.js b/packages/vats/src/core/boot-psm.js index 07b22226ef1..465f9c45ace 100644 --- a/packages/vats/src/core/boot-psm.js +++ b/packages/vats/src/core/boot-psm.js @@ -20,7 +20,8 @@ import { ECON_COMMITTEE_MANIFEST, startEconomicCommittee, } from '@agoric/inter-protocol/src/proposals/startEconCommittee.js'; -import { makeAgoricNamesAccess, makePromiseSpace } from './utils.js'; +import { makeAgoricNamesAccess } from './utils.js'; +import { makePromiseSpace } from './promise-space.js'; import { Stable, Stake } from '../tokens.js'; import { addBankAssets, @@ -40,8 +41,8 @@ import { noProvisioner, publishAgoricNames, startTimerService, + CHAIN_BOOTSTRAP_MANIFEST, } from './chain-behaviors.js'; -import { CHAIN_BOOTSTRAP_MANIFEST } from './manifest.js'; import { startWalletFactory, WALLET_FACTORY_MANIFEST, diff --git a/packages/vats/src/core/boot-sim.js b/packages/vats/src/core/boot-sim.js new file mode 100644 index 00000000000..d282912b482 --- /dev/null +++ b/packages/vats/src/core/boot-sim.js @@ -0,0 +1,43 @@ +// @ts-check +import { makeBootstrap } from './lib-boot.js'; + +import * as basicBehaviorsPlus from './basic-behaviors.js'; +import * as chainBehaviorsPlus from './chain-behaviors.js'; +import * as simBehaviorsPlus from './sim-behaviors.js'; +import * as utils from './utils.js'; + +const { BASIC_BOOTSTRAP_PERMITS: _b, ...basicBehaviors } = basicBehaviorsPlus; +const { + CHAIN_BOOTSTRAP_MANIFEST: _c, + SHARED_CHAIN_BOOTSTRAP_MANIFEST, + ...chainBehaviors +} = chainBehaviorsPlus; +const { SIM_CHAIN_BOOTSTRAP_PERMITS, ...simBehaviors } = simBehaviorsPlus; + +export const MANIFEST = { + ...SHARED_CHAIN_BOOTSTRAP_MANIFEST, + ...SIM_CHAIN_BOOTSTRAP_PERMITS, +}; + +const behaviors = { ...basicBehaviors, ...chainBehaviors, ...simBehaviors }; + +const modules = harden({ behaviors: { ...behaviors }, utils: { ...utils } }); + +/** + * Build root object of the bootstrap vat for the simulated chain. + * + * @param {{ + * D: DProxy, + * logger: (msg) => void, + * }} vatPowers + * @param {{ + * coreProposalCode?: string, + * }} vatParameters + */ +export const buildRootObject = (vatPowers, vatParameters) => { + console.debug(`sim bootstrap starting`); + + return makeBootstrap(vatPowers, vatParameters, MANIFEST, behaviors, modules); +}; + +harden({ buildRootObject }); diff --git a/packages/vats/src/core/boot-solo.js b/packages/vats/src/core/boot-solo.js new file mode 100644 index 00000000000..47d0ad02ba6 --- /dev/null +++ b/packages/vats/src/core/boot-solo.js @@ -0,0 +1,48 @@ +// @ts-check +import { makeBootstrap } from './lib-boot.js'; + +import * as basicBehaviorsPlus from './basic-behaviors.js'; +import * as chainBehaviorsPlus from './chain-behaviors.js'; +import * as clientBehaviorsPlus from './client-behaviors.js'; +import * as utils from './utils.js'; + +const { BASIC_BOOTSTRAP_PERMITS: _b, ...basicBehaviors } = basicBehaviorsPlus; +const { + CHAIN_BOOTSTRAP_MANIFEST: _c, + SHARED_CHAIN_BOOTSTRAP_MANIFEST: _s, + ...chainBehaviors +} = chainBehaviorsPlus; +const { CLIENT_BOOTSTRAP_MANIFEST, ...clientBehaviors } = clientBehaviorsPlus; +const behaviors = harden({ + ...basicBehaviors, + ...chainBehaviors, + ...clientBehaviors, +}); + +export const MANIFEST = CLIENT_BOOTSTRAP_MANIFEST; + +const modules = harden({ + clientBehaviors: { ...clientBehaviors }, + behaviors: { ...behaviors }, + utils: { ...utils }, +}); + +/** + * Build root object of the bootstrap vat. + * + * @param {{ + * D: DProxy, + * logger: (msg) => void, + * }} vatPowers + * @param {{ + * bootstrapManifest?: Record>, + * coreProposalCode?: string, + * }} vatParameters + */ +export const buildRootObject = (vatPowers, vatParameters) => { + console.debug(`solo client bootstrap starting`); + + return makeBootstrap(vatPowers, vatParameters, MANIFEST, behaviors, modules); +}; + +harden({ buildRootObject }); diff --git a/packages/vats/src/core/chain-behaviors.js b/packages/vats/src/core/chain-behaviors.js index cf9977943d3..c3ecadff0b0 100644 --- a/packages/vats/src/core/chain-behaviors.js +++ b/packages/vats/src/core/chain-behaviors.js @@ -19,7 +19,8 @@ import { allValues, BridgeId as BRIDGE_ID } from '@agoric/internal'; import * as STORAGE_PATH from '@agoric/internal/src/chain-storage-paths.js'; import { agoricNamesReserved, callProperties, extractPowers } from './utils.js'; -import { PowerFlags } from './basic-behaviors.js'; +import { BASIC_BOOTSTRAP_PERMITS } from './basic-behaviors.js'; +import { PowerFlags } from '../walletFlags.js'; const { Fail } = assert; const { keys } = Object; @@ -544,3 +545,97 @@ export const setupNetworkProtocols = async ({ await registerNetworkProtocols(vats, dibcBridgeManager); return E(client).assignBundle([_a => ({ ibcport: makePorts() })]); }; + +/** @type {import('./lib-boot').BootstrapManifest} */ +export const SHARED_CHAIN_BOOTSTRAP_MANIFEST = { + ...BASIC_BOOTSTRAP_PERMITS, + + [bridgeCoreEval.name]: true, // Needs all the powers. + [makeBridgeManager.name]: { + consume: { loadCriticalVat: true }, + devices: { bridge: 'kernel' }, + produce: { + bridgeManager: 'chainStorage', + provisionBridgeManager: 'chainStorage', + provisionWalletBridgeManager: 'chainStorage', + walletBridgeManager: 'chainStorage', + }, + }, + [startTimerService.name]: { + devices: { + timer: 'kernel', + }, + vats: { + timer: 'timer', + }, + consume: { client: true }, + produce: { + chainTimerService: 'timer', + }, + home: { produce: { chainTimerService: 'timer' } }, + }, + [makeChainStorage.name]: { + consume: { loadCriticalVat: true, bridgeManager: true }, + produce: { + chainStorage: 'chainStorage', + }, + }, + [publishAgoricNames.name]: { + consume: { + agoricNamesAdmin: true, + board: 'board', + chainStorage: 'chainStorage', + }, + }, + [makeProvisioner.name]: { + consume: { + loadCriticalVat: true, + clientCreator: true, + }, + produce: { + provisioning: 'provisioning', + }, + vats: { + comms: 'comms', + vattp: 'vattp', + }, + }, + [bridgeProvisioner.name]: { + consume: { + provisioning: true, + bridgeManager: 'chainStorage', + provisionBridgeManager: 'chainStorage', + provisionWalletBridgeManager: 'chainStorage', + }, + }, + [setupClientManager.name]: { + produce: { + client: true, + clientCreator: true, + }, + }, + [setupNetworkProtocols.name]: { + consume: { + client: true, + loadCriticalVat: true, + bridgeManager: 'chainStorage', + zoe: 'zoe', + provisioning: 'provisioning', + }, + produce: { + networkVat: 'network', + }, + }, +}; +harden(SHARED_CHAIN_BOOTSTRAP_MANIFEST); + +/** @type {import('./lib-boot.js').BootstrapManifest} */ +export const CHAIN_BOOTSTRAP_MANIFEST = harden({ + ...SHARED_CHAIN_BOOTSTRAP_MANIFEST, + [connectChainFaucet.name]: { + consume: { + client: true, + }, + home: { produce: { faucet: true } }, + }, +}); diff --git a/packages/vats/src/core/client-behaviors.js b/packages/vats/src/core/client-behaviors.js index dfd62fafd54..59b5c0f4820 100644 --- a/packages/vats/src/core/client-behaviors.js +++ b/packages/vats/src/core/client-behaviors.js @@ -4,8 +4,7 @@ import { makePluginManager } from '@agoric/swingset-vat/src/vats/plugin-manager. import { deeplyFulfilled } from '@endo/marshal'; import { observeNotifier } from '@agoric/notifier'; import { registerNetworkProtocols } from './chain-behaviors.js'; - -export { makeVatsFromBundles } from './basic-behaviors.js'; +import { makeVatsFromBundles } from './basic-behaviors.js'; const { Fail } = assert; @@ -164,3 +163,39 @@ export const startClient = async ({ await Promise.all([addLocalPresences(), addChainPresences()]); }; harden(startClient); + +/** @type {import('./lib-boot.js').BootstrapManifest} */ +export const CLIENT_BOOTSTRAP_MANIFEST = harden({ + /** @type {import('./lib-boot.js').BootstrapManifestPermit} */ + [makeVatsFromBundles.name]: { + vats: { + vatAdmin: 'vatAdmin', + }, + devices: { + vatAdmin: true, + }, + produce: { + vatAdminSvc: 'vatAdmin', + loadVat: true, + loadCriticalVat: true, + vatStore: true, + }, + }, + [startClient.name]: { + vatParameters: { + argv: { FIXME_GCI: true }, + }, + devices: { command: true, plugin: true, timer: true }, + vats: { + comms: true, + http: true, + network: true, + spawner: true, + timer: true, + uploads: true, + vattp: true, + }, + vatPowers: true, + consume: { vatAdminSvc: true }, + }, +}); diff --git a/packages/inter-protocol/src/proposals/demoIssuers.js b/packages/vats/src/core/demoIssuers.js similarity index 95% rename from packages/inter-protocol/src/proposals/demoIssuers.js rename to packages/vats/src/core/demoIssuers.js index c2b031dc25f..68eeab7227d 100644 --- a/packages/inter-protocol/src/proposals/demoIssuers.js +++ b/packages/vats/src/core/demoIssuers.js @@ -6,8 +6,9 @@ import { natSafeMath, } from '@agoric/zoe/src/contractSupport/index.js'; import { E, Far } from '@endo/far'; -import { Stake, Stable } from '@agoric/vats/src/tokens.js'; import { Nat } from '@endo/nat'; +import { notForProductionUse } from '@agoric/internal/src/magic-cookie-test-only.js'; +import { Stake, Stable } from '../tokens.js'; const { Fail, quote: q } = assert; const { multiply, floorDivide } = natSafeMath; @@ -20,7 +21,7 @@ export const DecimalPlaces = { ATOM: 6, WETH: 18, LINK: 18, - USDC: 18, + DAI: 18, moola: 2, simolean: 0, }; @@ -36,7 +37,9 @@ const FaucetPurseDetail = { balance: 53n, }, LINK: { proposedName: 'Oracle fee', balance: 51n }, - USDC: { proposedName: 'USD Coin', balance: 1_323n }, + // WAS: USDC, but that interacts poorly with loadgen + // TODO: move connectFaucet from sim-behaviors to a coreProposal + DAI: { proposedName: 'DAI', balance: 1_323n }, }; const PCT = 100n; @@ -146,7 +149,7 @@ export const AMMDemoState = { ], }, - USDC: { + DAI: { config: { ...defaultConfig, collateralValue: 10_000_000n, @@ -190,6 +193,7 @@ const mintRunPayment = async ( value, { centralSupplyInstall, feeMintAccess: feeMintAccessP, zoe }, ) => { + notForProductionUse(); const feeMintAccess = await feeMintAccessP; const { creatorFacet: ammSupplier } = await E(zoe).startInstance( @@ -297,6 +301,7 @@ export const connectFaucet = async ({ const { issuer, brand, mint } = await provideIssuerKit(issuerName); const unit = 10n ** BigInt(DecimalPlaces[issuerName]); const amount = AmountMath.make(brand, Nat(record.balance) * unit); + notForProductionUse(); const payment = await E(mint).mintPayment(amount); /** @type {UserPaymentRecord[]} */ @@ -321,7 +326,7 @@ export const connectFaucet = async ({ }), ); - const faucetPaymentInfo = userPaymentRecords.flat(); + const faucetPaymentInfo = harden(userPaymentRecords.flat()); const userFeePurse = await E(E(zoe).getFeeIssuer()).makeEmptyPurse(); @@ -457,7 +462,7 @@ export const poolRates = (issuerName, record, kits, central) => { }; /** - * @param { import('./econ-behaviors.js').EconomyBootstrapPowers & { + * @param { import('@agoric/inter-protocol/src/proposals/econ-behaviors.js').EconomyBootstrapPowers & { * consume: { mints } * }} powers */ diff --git a/packages/vats/src/core/boot.js b/packages/vats/src/core/lib-boot.js similarity index 64% rename from packages/vats/src/core/boot.js rename to packages/vats/src/core/lib-boot.js index 72622efdb78..61d2f7cab01 100644 --- a/packages/vats/src/core/boot.js +++ b/packages/vats/src/core/lib-boot.js @@ -1,70 +1,67 @@ // @ts-check import { E, Far } from '@endo/far'; - -import * as simBehaviors from '@agoric/inter-protocol/src/proposals/sim-behaviors.js'; import { makePassableEncoding } from '@agoric/swingset-vat/tools/passableEncoding.js'; -import { - makeAgoricNamesAccess, - makePromiseSpace, - runModuleBehaviors, -} from './utils.js'; -import { - CLIENT_BOOTSTRAP_MANIFEST, - CHAIN_BOOTSTRAP_MANIFEST, - SIM_CHAIN_BOOTSTRAP_MANIFEST, -} from './manifest.js'; - -import * as behaviors from './behaviors.js'; -import * as clientBehaviors from './client-behaviors.js'; -import * as utils from './utils.js'; +import { makeAgoricNamesAccess, runModuleBehaviors } from './utils.js'; +import { makePromiseSpace } from './promise-space.js'; const { Fail, quote: q } = assert; -// Choose a manifest based on runtime configured argv.ROLE. -const roleToManifest = harden({ - chain: CHAIN_BOOTSTRAP_MANIFEST, - 'sim-chain': SIM_CHAIN_BOOTSTRAP_MANIFEST, - client: CLIENT_BOOTSTRAP_MANIFEST, -}); -const roleToBehaviors = harden({ - 'sim-chain': { ...behaviors, ...simBehaviors }, - // copy to avoid trying to harden a module namespace - client: { ...clientBehaviors }, -}); +/** + * @typedef {true | string | { [key: string]: BootstrapManifestPermit }} BootstrapManifestPermit + */ /** - * Build root object of the bootstrap vat. + * A manifest is an object in which each key is the name of a function to run + * at bootstrap and the corresponding value is a "permit" describing an + * attenuation of allPowers that should be provided as its first argument + * (cf. packages/vats/src/core/boot.js). + * + * A permit is either + * - `true` or a string (both meaning no attenuation, with a string serving + * as a grouping label for convenience and diagram generation), or + * - an object whose keys identify properties to preserve and whose values + * are themselves (recursive) permits. + * + * @typedef {Record} BootstrapManifest * + */ + +/** + * @typedef {(powers: *, config?: *) => Promise} BootBehavior + * @typedef {Record} ModuleNamespace + * @typedef {{ utils: typeof import('./utils.js') } & Record>} BootModules + */ + +/** @type {(a: X[], b: X[]) => X[]} */ +const setDiff = (a, b) => a.filter(x => !b.includes(x)); + +/** * @param {{ * D: DProxy, * logger: (msg) => void, * }} vatPowers - * @param {{ - * argv: { ROLE: string }, - * bootstrapManifest?: Record>, - * coreProposalCode?: string, - * }} vatParameters + * @param {Record} vatParameters + * @param {BootstrapManifest} bootManifest + * @param {Record} behaviors + * @param {BootModules} modules */ -const buildRootObject = (vatPowers, vatParameters) => { +export const makeBootstrap = ( + vatPowers, + vatParameters, + bootManifest, + behaviors, + modules, +) => { + const { keys } = Object; + const extra = setDiff(keys(bootManifest), keys(behaviors)); + extra.length === 0 || Fail`missing behavior for manifest keys: ${extra}`; + const log = vatPowers.logger || console.info; const { produce, consume } = makePromiseSpace(log); const { agoricNames, agoricNamesAdmin, spaces } = makeAgoricNamesAccess(log); produce.agoricNames.resolve(agoricNames); produce.agoricNamesAdmin.resolve(agoricNamesAdmin); - const { - // XXX not for production ?! - argv: { ROLE = 'chain' }, - bootstrapManifest, - } = vatParameters; - // ROLE || Fail`boot requires ROLE in argv`; - console.debug(`${ROLE} bootstrap starting`); - - const bootManifest = bootstrapManifest || roleToManifest[ROLE]; - const bootBehaviors = roleToBehaviors[ROLE] || behaviors; - bootManifest || Fail`no configured bootstrapManifest for role ${ROLE}`; - bootBehaviors || Fail`no configured bootstrapBehaviors for role ${ROLE}`; - /** * Bootstrap vats and devices. * @@ -74,19 +71,18 @@ const buildRootObject = (vatPowers, vatParameters) => { const rawBootstrap = async (vats, devices) => { // Complete SwingSet wiring. const { D } = vatPowers; - if (devices.mailbox) { - D(devices.mailbox).registerInboundHandler(vats.vattp); - // eslint-disable-next-line @jessie.js/no-nested-await -- XXX - await E(vats.vattp).registerMailboxDevice(devices.mailbox); - } else { + if (!devices.mailbox) { console.warn('No mailbox device. Not registering with vattp'); } + await (devices.mailbox && + (D(devices.mailbox).registerInboundHandler(vats.vattp), + E(vats.vattp).registerMailboxDevice(devices.mailbox))); const runBehaviors = manifest => { return runModuleBehaviors({ // eslint-disable-next-line no-use-before-define allPowers, - behaviors: bootBehaviors, + behaviors, manifest, makeConfig: (name, permit) => { log(`bootstrap: ${name}(${q(permit)}`); @@ -106,12 +102,7 @@ const buildRootObject = (vatPowers, vatParameters) => { ...spaces, runBehaviors, // These module namespaces might be useful for core eval governance. - modules: { - clientBehaviors: { ...clientBehaviors }, - simBehaviors: { ...simBehaviors }, - behaviors: { ...behaviors }, - utils: { ...utils }, - }, + modules, }); await runBehaviors(bootManifest); @@ -190,6 +181,3 @@ const buildRootObject = (vatPowers, vatParameters) => { // #endregion }); }; - -harden({ buildRootObject }); -export { buildRootObject }; diff --git a/packages/vats/src/core/manifest.js b/packages/vats/src/core/manifest.js deleted file mode 100644 index ebd0ac94717..00000000000 --- a/packages/vats/src/core/manifest.js +++ /dev/null @@ -1,328 +0,0 @@ -// @ts-check - -import { - connectFaucet, - grantRunBehaviors, - installSimEgress, -} from '@agoric/inter-protocol/src/proposals/sim-behaviors.js'; -import { makeBoard } from '../lib-board.js'; -import { - addBankAssets, - buildZoe, - installBootContracts, - makeAddressNameHubs, - makeClientBanks, - makeOracleBrands, - makeVatsFromBundles, - mintInitialSupply, - startPriceAuthority, -} from './basic-behaviors.js'; -import { - bridgeProvisioner, - connectChainFaucet, - makeBridgeManager, - makeChainStorage, - makeProvisioner, - publishAgoricNames, - setupClientManager, - setupNetworkProtocols, - startTimerService, -} from './chain-behaviors.js'; -import { startClient } from './client-behaviors.js'; - -/** - * @typedef {true | string | { [key: string]: BootstrapManifestPermit }} BootstrapManifestPermit - */ - -/** - * A manifest is an object in which each key is the name of a function to run - * at bootstrap and the corresponding value is a "permit" describing an - * attenuation of allPowers that should be provided as its first argument - * (cf. packages/vats/src/core/boot.js). - * - * A permit is either - * - `true` or a string (both meaning no attenuation, with a string serving - * as a grouping label for convenience and diagram generation), or - * - an object whose keys identify properties to preserve and whose values - * are themselves (recursive) permits. - * - * @typedef {Record} BootstrapManifest - */ - -/** @type {BootstrapManifest} */ -const SHARED_CHAIN_BOOTSTRAP_MANIFEST = harden({ - /** @type {BootstrapManifestPermit} */ - bridgeCoreEval: true, // Needs all the powers. - [makeOracleBrands.name]: { - oracleBrand: { - produce: { - USD: true, - }, - }, - }, - [startPriceAuthority.name]: { - consume: { loadCriticalVat: true, client: true }, - produce: { - priceAuthorityVat: 'priceAuthority', - priceAuthority: 'priceAuthority', - priceAuthorityAdmin: 'priceAuthority', - }, - }, - [makeVatsFromBundles.name]: { - vats: { - vatAdmin: 'vatAdmin', - }, - devices: { - vatAdmin: true, - }, - produce: { - vatAdminSvc: 'vatAdmin', - loadVat: true, - loadCriticalVat: true, - vatStore: true, - }, - }, - [buildZoe.name]: { - consume: { - vatAdminSvc: true, - loadCriticalVat: true, - client: true, - }, - produce: { - zoe: 'zoe', - feeMintAccess: 'zoe', - }, - issuer: { produce: { Invitation: 'zoe' } }, - brand: { produce: { Invitation: 'zoe' } }, - }, - [makeBoard.name]: { - consume: { - loadCriticalVat: true, - client: true, - }, - produce: { - board: 'board', - }, - }, - [makeBridgeManager.name]: { - consume: { loadCriticalVat: true }, - devices: { bridge: 'kernel' }, - produce: { - bridgeManager: true, - provisionBridgeManager: true, - provisionWalletBridgeManager: true, - walletBridgeManager: true, - }, - }, - [makeAddressNameHubs.name]: { - consume: { - agoricNames: true, - client: true, - }, - produce: { - namesByAddress: true, - namesByAddressAdmin: true, - }, - home: { - produce: { myAddressNameAdmin: true }, - }, - }, - [startTimerService.name]: { - devices: { - timer: true, - }, - vats: { - timer: 'timer', - }, - consume: { client: true }, - produce: { - chainTimerService: 'timer', - }, - home: { produce: { chainTimerService: 'timer' } }, - }, - [makeChainStorage.name]: { - consume: { loadCriticalVat: true, bridgeManager: true }, - produce: { - chainStorage: 'chainStorage', - }, - }, - [publishAgoricNames.name]: { - consume: { - agoricNamesAdmin: true, - board: 'board', - chainStorage: 'chainStorage', - }, - }, - [makeClientBanks.name]: { - consume: { - namesByAddressAdmin: true, - bankManager: 'bank', - client: true, - walletFactoryStartResult: 'walletFactory', - }, - home: { produce: { bank: 'bank' } }, - }, - [installBootContracts.name]: { - consume: { zoe: 'zoe', vatAdminSvc: true }, - installation: { - produce: { - centralSupply: 'zoe', - mintHolder: 'zoe', - }, - }, - }, - [mintInitialSupply.name]: { - vatParameters: { - argv: { bootMsg: true }, - }, - consume: { - feeMintAccess: true, - zoe: true, - }, - produce: { - initialSupply: true, - }, - installation: { - consume: { centralSupply: 'zoe' }, - }, - }, - [addBankAssets.name]: { - consume: { - agoricNamesAdmin: true, - initialSupply: true, - bridgeManager: true, - // TODO: re-org loadCriticalVat to be subject to permits - loadCriticalVat: true, - zoe: true, - }, - produce: { - bankManager: 'bank', - bldIssuerKit: true, - }, - installation: { - consume: { centralSupply: 'zoe', mintHolder: 'zoe' }, - }, - issuer: { produce: { BLD: 'BLD', IST: 'zoe' } }, - brand: { produce: { BLD: 'BLD', IST: 'zoe' } }, - }, - [makeProvisioner.name]: { - consume: { - loadCriticalVat: true, - clientCreator: true, - }, - produce: { - provisioning: 'provisioning', - }, - vats: { - comms: true, - vattp: true, - }, - }, - [bridgeProvisioner.name]: { - consume: { - provisioning: true, - bridgeManager: true, - provisionBridgeManager: true, - provisionWalletBridgeManager: true, - }, - }, - [setupClientManager.name]: { - produce: { - client: true, - clientCreator: true, - }, - }, - [setupNetworkProtocols.name]: { - consume: { - client: true, - loadCriticalVat: true, - bridgeManager: true, - zoe: true, - provisioning: true, - }, - produce: { - networkVat: true, - }, - }, -}); - -/** @type {BootstrapManifest} */ -export const CHAIN_BOOTSTRAP_MANIFEST = harden({ - ...SHARED_CHAIN_BOOTSTRAP_MANIFEST, - [connectChainFaucet.name]: { - consume: { - client: true, - }, - home: { produce: { faucet: true } }, - }, -}); - -/** @type {BootstrapManifest} */ -export const CLIENT_BOOTSTRAP_MANIFEST = harden({ - /** @type {BootstrapManifestPermit} */ - [makeVatsFromBundles.name]: { - vats: { - vatAdmin: 'vatAdmin', - }, - devices: { - vatAdmin: true, - }, - produce: { - vatAdminSvc: 'vatAdmin', - loadVat: true, - loadCriticalVat: true, - vatStore: true, - }, - }, - [startClient.name]: { - vatParameters: { - argv: { FIXME_GCI: true }, - }, - devices: { command: true, plugin: true, timer: true }, - vats: { - comms: true, - http: true, - network: true, - spawner: true, - timer: true, - uploads: true, - vattp: true, - }, - vatPowers: true, - consume: { vatAdminSvc: true }, - }, -}); - -/** @type {BootstrapManifest} */ -export const SIM_CHAIN_BOOTSTRAP_MANIFEST = harden({ - ...SHARED_CHAIN_BOOTSTRAP_MANIFEST, - /** @type {BootstrapManifestPermit} */ - [installSimEgress.name]: { - vatParameters: { argv: { hardcodedClientAddresses: true } }, - vats: { - vattp: true, - comms: true, - }, - consume: { clientCreator: true }, - }, - [connectFaucet.name]: { - consume: { - bankManager: true, - bldIssuerKit: true, - client: true, - feeMintAccess: true, - loadVat: true, - zoe: true, - }, - installation: { - consume: { centralSupply: 'zoe' }, - }, - produce: { mints: true }, - home: { produce: { faucet: true } }, - }, - [grantRunBehaviors.name]: { - runBehaviors: true, - consume: { client: true }, - home: { produce: { runBehaviors: true, governanceActions: true } }, - }, -}); diff --git a/packages/vats/src/core/promise-space.js b/packages/vats/src/core/promise-space.js new file mode 100644 index 00000000000..93e9fc3c023 --- /dev/null +++ b/packages/vats/src/core/promise-space.js @@ -0,0 +1,106 @@ +import { makePromiseKit } from '@endo/promise-kit'; + +/** + * Make { produce, consume } where for each name, `consume[name]` is a promise + * and `produce[name].resolve` resolves it. + * + * Note: repeated resolves() are noops. + * + * @param {typeof console.log} [log] + * @returns {PromiseSpace} + */ + +export const makePromiseSpace = (log = (..._args) => {}) => { + /** + * @typedef {PromiseRecord & { + * reset: (reason?: unknown) => void, + * isSettling: boolean, + * }} PromiseState + */ + /** @type {Map} */ + const nameToState = new Map(); + const remaining = new Set(); + + const findOrCreateState = name => { + /** @type {PromiseState} */ + let state; + const currentState = nameToState.get(name); + if (currentState) { + state = currentState; + } else { + log(`${name}: new Promise`); + const pk = makePromiseKit(); + + pk.promise + .finally(() => { + remaining.delete(name); + log(name, 'settled; remaining:', [...remaining.keys()].sort()); + }) + .catch(() => {}); + + const settling = () => { + assert(state); + state = harden({ ...state, isSettling: true }); + nameToState.set(name, state); + }; + + const resolve = value => { + settling(); + pk.resolve(value); + }; + const reject = reason => { + settling(); + pk.reject(reason); + }; + + const reset = (reason = undefined) => { + if (!state.isSettling) { + if (!reason) { + // Reuse the old promise; don't reject it. + return; + } + reject(reason); + } + // Now publish a new promise. + nameToState.delete(name); + remaining.delete(name); + }; + + state = harden({ + isSettling: false, + resolve, + reject, + reset, + promise: pk.promise, + }); + nameToState.set(name, state); + remaining.add(name); + } + return state; + }; + + const consume = new Proxy( + {}, + { + get: (_target, name) => { + assert.typeof(name, 'string'); + const kit = findOrCreateState(name); + return kit.promise; + }, + }, + ); + + const produce = new Proxy( + {}, + { + get: (_target, name) => { + assert.typeof(name, 'string'); + const { reject, resolve, reset } = findOrCreateState(name); + return harden({ reject, resolve, reset }); + }, + }, + ); + + return harden({ produce, consume }); +}; +harden(makePromiseSpace); diff --git a/packages/vats/src/core/sim-behaviors.js b/packages/vats/src/core/sim-behaviors.js new file mode 100644 index 00000000000..bfafac62be6 --- /dev/null +++ b/packages/vats/src/core/sim-behaviors.js @@ -0,0 +1,73 @@ +import { E, Far } from '@endo/far'; +import { connectFaucet } from './demoIssuers.js'; +import { addRemote } from './utils.js'; + +export { connectFaucet }; + +/** @param {BootstrapPowers} powers */ +export const installSimEgress = async ({ + vatParameters: { argv }, + vats: { vattp, comms }, + consume: { clientCreator }, +}) => { + const PROVISIONER_INDEX = 1; + + await Promise.all( + (argv?.hardcodedClientAddresses || []).map(async (addr, i) => { + const clientFacet = await E(clientCreator).createClientFacet( + `solo${i}`, + addr, + ['agoric.ALL_THE_POWERS'], + ); + + await addRemote(addr, { vats: { comms, vattp } }); + await E(comms).addEgress(addr, PROVISIONER_INDEX, clientFacet); + }), + ); +}; +harden(installSimEgress); + +/** @param {BootstrapPowers} powers */ +export const grantRunBehaviors = async ({ + runBehaviors, + consume: { client }, +}) => { + const bundle = { + behaviors: Far('behaviors', { run: manifest => runBehaviors(manifest) }), + }; + return E(client).assignBundle([_addr => bundle]); +}; +harden(grantRunBehaviors); + +/** @type {import('./lib-boot').BootstrapManifest} */ +export const SIM_CHAIN_BOOTSTRAP_PERMITS = { + [installSimEgress.name]: { + vatParameters: { argv: { hardcodedClientAddresses: true } }, + vats: { + vattp: true, + comms: true, + }, + consume: { clientCreator: true }, + }, + [connectFaucet.name]: { + consume: { + bankManager: true, + bldIssuerKit: true, + client: true, + feeMintAccess: true, + loadVat: true, + zoe: true, + }, + installation: { + consume: { centralSupply: 'zoe' }, + }, + produce: { mints: true }, + home: { produce: { faucet: true } }, + }, + [grantRunBehaviors.name]: { + runBehaviors: true, + consume: { client: true }, + home: { produce: { runBehaviors: true, governanceActions: true } }, + }, +}; +harden(SIM_CHAIN_BOOTSTRAP_PERMITS); diff --git a/packages/vats/src/core/types.js b/packages/vats/src/core/types.js index d0e134ad055..913f9177ae5 100644 --- a/packages/vats/src/core/types.js +++ b/packages/vats/src/core/types.js @@ -226,13 +226,10 @@ * IDEA/TODO: make types of demo stuff invisible in production behaviors * @typedef {{ * argv: { - * ROLE: string, - * hardcodedClientAddresses: string[], + * hardcodedClientAddresses?: string[], * FIXME_GCI: string, - * PROVISIONER_INDEX: number, + * PROVISIONER_INDEX?: number, * }, - * bootstrapManifest?: Record>, - * governanceActions?: boolean, * }} BootstrapVatParams * @typedef { BootstrapSpace & { * devices: SoloDevices | ChainDevices, diff --git a/packages/vats/src/core/utils.js b/packages/vats/src/core/utils.js index 19019da63e2..40122399877 100644 --- a/packages/vats/src/core/utils.js +++ b/packages/vats/src/core/utils.js @@ -1,9 +1,10 @@ // @ts-check -import { E } from '@endo/far'; +import { E, Far } from '@endo/far'; import { assertPassable } from '@endo/marshal'; -import { makePromiseKit } from '@endo/promise-kit'; +import { WalletName } from '@agoric/internal'; import { makeNameHubKit } from '../nameHub.js'; import { Stable, Stake } from '../tokens.js'; +import { makePromiseSpace } from './promise-space.js'; const { entries, fromEntries, keys } = Object; const { Fail, quote: q } = assert; @@ -109,110 +110,6 @@ harden(addRemote); export const callProperties = (builders, ...args) => fromEntries(builders.map(fn => entries(fn(...args))).flat()); -/** - * Make { produce, consume } where for each name, `consume[name]` is a promise - * and `produce[name].resolve` resolves it. - * - * Note: repeated resolves() are noops. - * - * @param {typeof console.log} [log] - * @returns {PromiseSpace} - */ -export const makePromiseSpace = (log = (..._args) => {}) => { - /** - * @typedef {PromiseRecord & { - * reset: (reason?: unknown) => void, - * isSettling: boolean, - * }} PromiseState - */ - /** @type {Map} */ - const nameToState = new Map(); - const remaining = new Set(); - - const findOrCreateState = name => { - /** @type {PromiseState} */ - let state; - const currentState = nameToState.get(name); - if (currentState) { - state = currentState; - } else { - log(`${name}: new Promise`); - const pk = makePromiseKit(); - - pk.promise - .finally(() => { - remaining.delete(name); - log(name, 'settled; remaining:', [...remaining.keys()].sort()); - }) - .catch(() => {}); - - const settling = () => { - assert(state); - state = harden({ ...state, isSettling: true }); - nameToState.set(name, state); - }; - - const resolve = value => { - settling(); - pk.resolve(value); - }; - const reject = reason => { - settling(); - pk.reject(reason); - }; - - const reset = (reason = undefined) => { - if (!state.isSettling) { - if (!reason) { - // Reuse the old promise; don't reject it. - return; - } - reject(reason); - } - // Now publish a new promise. - nameToState.delete(name); - remaining.delete(name); - }; - - state = harden({ - isSettling: false, - resolve, - reject, - reset, - promise: pk.promise, - }); - nameToState.set(name, state); - remaining.add(name); - } - return state; - }; - - const consume = new Proxy( - {}, - { - get: (_target, name) => { - assert.typeof(name, 'string'); - const kit = findOrCreateState(name); - return kit.promise; - }, - }, - ); - - const produce = new Proxy( - {}, - { - get: (_target, name) => { - assert.typeof(name, 'string'); - const { reject, resolve, reset } = findOrCreateState(name); - return harden({ reject, resolve, reset }); - }, - }, - ); - - return harden({ produce, consume }); -}; -harden(makePromiseSpace); - /** * Attenuate `specimen` to only allow acccess to properties specified in `template` * @@ -360,3 +257,21 @@ export const makeAgoricNamesAccess = ( spaces: typedSpaces, }; }; + +/** + * @param {string} address + */ +export const makeMyAddressNameAdminKit = address => { + // Create a name hub for this address. + const { nameHub, nameAdmin: rawMyAddressNameAdmin } = makeNameHubKit(); + + /** @type {import('../types').MyAddressNameAdmin} */ + const myAddressNameAdmin = Far('myAddressNameAdmin', { + ...rawMyAddressNameAdmin, + getMyAddress: () => address, + }); + // reserve space for deposit facet + myAddressNameAdmin.reserve(WalletName.depositFacet); + + return { nameHub, myAddressNameAdmin }; +}; diff --git a/packages/vats/src/nameHub.js b/packages/vats/src/nameHub.js index f270619a8bb..7606b0edcad 100644 --- a/packages/vats/src/nameHub.js +++ b/packages/vats/src/nameHub.js @@ -38,7 +38,7 @@ export const makeNameHubKit = () => { return E(firstValue).lookup(...remaining); }, entries: () => { - return [ + return harden([ ...mapIterable( keyToRecord.entries(), ([key, record]) => @@ -47,7 +47,7 @@ export const makeNameHubKit = () => { record.promise || record.value, ]), ), - ]; + ]); }, values: () => { return [ @@ -58,7 +58,7 @@ export const makeNameHubKit = () => { ]; }, keys: () => { - return [...keyToRecord.keys()]; + return harden([...keyToRecord.keys()]); }, }); diff --git a/packages/vats/src/provisionPool.js b/packages/vats/src/provisionPool.js index 11ebf601e19..38593f8ad8f 100644 --- a/packages/vats/src/provisionPool.js +++ b/packages/vats/src/provisionPool.js @@ -8,7 +8,7 @@ import { observeIteration, observeNotifier } from '@agoric/notifier'; import { AmountMath } from '@agoric/ertp'; import { handleParamGovernance, ParamTypes } from '@agoric/governance'; import { makeMetricsPublishKit } from '@agoric/inter-protocol/src/contractSupport.js'; -import { PowerFlags } from './core/basic-behaviors.js'; +import { PowerFlags } from './walletFlags.js'; const { details: X, quote: q } = assert; diff --git a/packages/vats/src/vat-mints.js b/packages/vats/src/vat-mints.js index 72034cda1e9..31dfa0d6c69 100644 --- a/packages/vats/src/vat-mints.js +++ b/packages/vats/src/vat-mints.js @@ -3,6 +3,7 @@ import { Far } from '@endo/far'; import { makeIssuerKit, AmountMath } from '@agoric/ertp'; import { makeScalarMapStore } from '@agoric/store'; +import { notForProductionUse } from '@agoric/internal/src/magic-cookie-test-only.js'; // This vat contains two starting mints for demos: moolaMint and // simoleanMint. @@ -21,13 +22,16 @@ export function buildRootObject() { getIssuers: issuerNames => issuerNames.map(api.getIssuer), /** - * NOTE: a mint is ability to mint new digital assets, + * WARNING: a mint is ability to mint new digital assets, * a very powerful authority that is usually closely held. * But this mint is for demo / faucet purposes. * * @param {string} name */ - getMint: name => mintsAndBrands.get(name).mint, + getMint: name => { + notForProductionUse(); + return mintsAndBrands.get(name).mint; + }, /** @param {string[]} issuerNames */ getMints: issuerNames => issuerNames.map(api.getMint), /** diff --git a/packages/vats/src/walletFlags.js b/packages/vats/src/walletFlags.js new file mode 100644 index 00000000000..85bbed360da --- /dev/null +++ b/packages/vats/src/walletFlags.js @@ -0,0 +1,12 @@ +// XXX domain of @agoric/cosmic-proto +/** + * non-exhaustive list of powerFlags + * REMOTE_WALLET is currently a default. + * + * See also MsgProvision in golang/cosmos/proto/agoric/swingset/msgs.proto + */ +export const PowerFlags = /** @type {const} */ ({ + SMART_WALLET: 'SMART_WALLET', + /** The ag-solo wallet is remote. */ + REMOTE_WALLET: 'REMOTE_WALLET', +}); diff --git a/packages/vats/test/bootstrapTests/supports.js b/packages/vats/test/bootstrapTests/supports.js index 96666f0f53d..5fcc3bfbaca 100644 --- a/packages/vats/test/bootstrapTests/supports.js +++ b/packages/vats/test/bootstrapTests/supports.js @@ -11,7 +11,7 @@ import { promises as fs } from 'fs'; import { resolve as importMetaResolve } from 'import-meta-resolve'; import { boardSlottingMarshaller } from '../../tools/board-utils.js'; -/** @typedef {ReturnType} BootstrapRootObject */ +/** @typedef {ReturnType} BootstrapRootObject */ /** @type {Record} */ export const bootstrapMethods = { @@ -186,11 +186,12 @@ export const makeWalletFactoryDriver = async ( }; }; -export const getNodeTestVaultsConfig = async () => { - const fullPath = await importMetaResolve( - '@agoric/vats/decentral-test-vaults-config.json', - import.meta.url, - ).then(u => new URL(u).pathname); +export const getNodeTestVaultsConfig = async ( + specifier = '@agoric/vats/decentral-test-vaults-config.json', +) => { + const fullPath = await importMetaResolve(specifier, import.meta.url).then( + u => new URL(u).pathname, + ); const config = await loadSwingsetConfigFile(fullPath); assert(config); @@ -217,10 +218,11 @@ export const getNodeTestVaultsConfig = async () => { * factory metrics using separate collateral managers. (Or use test.serial) * * @param {import('ava').ExecutionContext} t + * @param {string} [specifier] bootstrap config specifier */ -export const makeSwingsetTestKit = async t => { +export const makeSwingsetTestKit = async (t, specifier) => { console.time('makeSwingsetTestKit'); - const configPath = await getNodeTestVaultsConfig(); + const configPath = await getNodeTestVaultsConfig(specifier); const { kernelStorage } = initSwingStore(); const storage = makeFakeStorageKit('bootstrapTests'); @@ -282,8 +284,8 @@ export const makeSwingsetTestKit = async t => { bridgeOutbound, kernelStorage, configPath, - [], - { ROLE: 'chain' }, + {}, + {}, { debugName: 'TESTBOOT' }, ); console.timeLog('makeSwingsetTestKit', 'buildSwingset'); diff --git a/packages/vats/test/bootstrapTests/test-demo-config.js b/packages/vats/test/bootstrapTests/test-demo-config.js new file mode 100644 index 00000000000..3a900a75230 --- /dev/null +++ b/packages/vats/test/bootstrapTests/test-demo-config.js @@ -0,0 +1,97 @@ +// @ts-check +import { test as anyTest } from '@agoric/zoe/tools/prepare-test-env-ava.js'; +import { PowerFlags } from '../../src/walletFlags.js'; + +import { makeSwingsetTestKit } from './supports.js'; + +const { keys } = Object; +/** + * @type {import('ava').TestFn>>} + */ +const test = anyTest; + +const makeDefaultTestContext = async t => { + const swingsetTestKit = await makeSwingsetTestKit( + t, + '@agoric/vats/decentral-demo-config.json', + ); + return swingsetTestKit; +}; + +test.before(async t => (t.context = await makeDefaultTestContext(t))); + +// Goal: test that prod config does not expose mailbox access. +// But on the JS side, aside from vattp, prod config exposes mailbox access +// just as much as dev, so we can't test that here. + +const makeHomeFor = async (addr, EV) => { + const clientCreator = await EV.vat('bootstrap').consumeItem('clientCreator'); + const clientFacet = await EV(clientCreator).createClientFacet( + 'user1', + addr, + PowerFlags.REMOTE_WALLET, + ); + return EV(clientFacet).getChainBundle(); +}; + +test('sim/demo config provides home with .myAddressNameAdmin', async t => { + const devToolKeys = [ + 'behaviors', + 'chainTimerService', + 'faucet', + 'priceAuthorityAdminFacet', + 'vaultFactoryCreatorFacet', + ]; + + // TODO: cross-check these with docs and/or deploy-script-support + const homeKeys = [ + 'agoricNames', + 'attMaker', + 'bank', + 'board', + 'ibcport', + 'myAddressNameAdmin', + 'namesByAddress', + 'priceAuthority', + 'zoe', + ...devToolKeys, + ].sort(); + + const { EV } = t.context.runUtils; + await t.notThrowsAsync(EV.vat('bootstrap').consumeItem('provisioning')); + t.log('bootstrap produced provisioning vat'); + const addr = 'agoric123'; + const home = await makeHomeFor(addr, EV); + const actual = await EV(home.myAddressNameAdmin).getMyAddress(); + t.is(actual, addr, 'my address'); + t.deepEqual(keys(home).sort(), homeKeys); +}); + +test('sim/demo config launches Vaults as expected by loadgen', async t => { + const { EV } = t.context.runUtils; + const agoricNames = await EV.vat('bootstrap').consumeItem('agoricNames'); + const vaultsInstance = await EV(agoricNames).lookup( + 'instance', + 'VaultFactory', + ); + t.truthy(vaultsInstance); +}); + +/** + * decentral-demo-config.json now uses boot-sim.js, + * which includes connectFaucet, which re-introduced USDC. + * That triggered a compatibility path in the loadgen that + * caused it to try and fail to run the vaults task. + * work-around: rename USDC to DAI in connectFaucet. + * + * TODO: move connectFaucet to a coreProposal and + * separate decentral-demo-config.json into separate + * configurations for sim-chain, loadgen. + */ +test('demo config meets loadgen constraint: no USDC', async t => { + const { EV } = t.context.runUtils; + const home = await makeHomeFor('addr123', EV); + const pmtInfo = await EV(home.faucet).tapFaucet(); + const found = pmtInfo.find(p => p.issuerPetname === 'USDC'); + t.deepEqual(found, undefined); +}); diff --git a/packages/vats/test/snapshots/test-boot-config.js.md b/packages/vats/test/snapshots/test-boot-config.js.md new file mode 100644 index 00000000000..7c9cb37e562 --- /dev/null +++ b/packages/vats/test/snapshots/test-boot-config.js.md @@ -0,0 +1,279 @@ +# Snapshot report for `test/test-boot-config.js` + +The actual snapshot is saved in `test-boot-config.js.snap`. + +Generated by [AVA](https://avajs.dev). + +## bootstrap permit visualization snapshot + +> Snapshot 1 + + `digraph G {␊ + rankdir = LR;␊ + subgraph cluster_priceAuthority {␊ + label = "priceAuthority"␊ + "space_priceAuthorityVat" [label="priceAuthorityVat", shape=house, style=filled, fillcolor=khaki];␊ + "space_priceAuthority" [label="priceAuthority", shape=house, style=filled, fillcolor=khaki];␊ + "space_priceAuthorityAdmin" [label="priceAuthorityAdmin", shape=house, style=filled, fillcolor=khaki];␊ + }␊ + subgraph cluster_vatAdmin {␊ + label = "vatAdmin"␊ + "vats_vatAdmin" [label="vatAdmin", shape=doubleoctagon, style=filled, fillcolor=tomato];␊ + "space_vatAdminSvc" [label="vatAdminSvc", shape=house, style=filled, fillcolor=khaki];␊ + }␊ + subgraph cluster_kernel {␊ + label = "kernel"␊ + "devices_vatAdmin" [label="vatAdmin", shape=box, style=filled, fillcolor=gold];␊ + "devices_bridge" [label="bridge", shape=box, style=filled, fillcolor=gold];␊ + "devices_timer" [label="timer", shape=box, style=filled, fillcolor=gold];␊ + }␊ + subgraph cluster_zoe {␊ + label = "zoe"␊ + "space_zoe" [label="zoe", shape=house, style=filled, fillcolor=khaki];␊ + "space_feeMintAccess" [label="feeMintAccess", shape=house, style=filled, fillcolor=khaki];␊ + "issuer_Invitation" [label="Invitation", shape=trapezium, style=filled, fillcolor=chocolate];␊ + "brand_Invitation" [label="Invitation", shape=Mcircle, style=filled, fillcolor=chocolate2];␊ + "space_zoe" [label="zoe", shape=house, style=filled, fillcolor=khaki];␊ + "installation_centralSupply" [label="centralSupply", shape=cylinder];␊ + "installation_mintHolder" [label="mintHolder", shape=cylinder];␊ + "installation_centralSupply" [label="centralSupply", shape=cylinder];␊ + "issuer_IST" [label="IST", shape=trapezium, style=filled, fillcolor=chocolate];␊ + "brand_IST" [label="IST", shape=Mcircle, style=filled, fillcolor=chocolate2];␊ + "installation_centralSupply" [label="centralSupply", shape=cylinder];␊ + "installation_mintHolder" [label="mintHolder", shape=cylinder];␊ + "space_zoe" [label="zoe", shape=house, style=filled, fillcolor=khaki];␊ + }␊ + subgraph cluster_board {␊ + label = "board"␊ + "space_board" [label="board", shape=house, style=filled, fillcolor=khaki];␊ + "space_board" [label="board", shape=house, style=filled, fillcolor=khaki];␊ + }␊ + subgraph cluster_provisioning {␊ + label = "provisioning"␊ + "home" [label="home"];␊ + "home" [label="home"];␊ + "home" [label="home"];␊ + "space_provisioning" [label="provisioning", shape=house, style=filled, fillcolor=khaki];␊ + "space_provisioning" [label="provisioning", shape=house, style=filled, fillcolor=khaki];␊ + "home" [label="home"];␊ + }␊ + subgraph cluster_bank {␊ + label = "bank"␊ + "space_bankManager" [label="bankManager", shape=house, style=filled, fillcolor=khaki];␊ + "home_bank" [label="bank", shape=folder];␊ + "space_bankManager" [label="bankManager", shape=house, style=filled, fillcolor=khaki];␊ + }␊ + subgraph cluster_walletFactory {␊ + label = "walletFactory"␊ + "space_walletFactoryStartResult" [label="walletFactoryStartResult", shape=house, style=filled, fillcolor=khaki];␊ + }␊ + subgraph cluster_chainStorage {␊ + label = "chainStorage"␊ + "space_bridgeManager" [label="bridgeManager", shape=house, style=filled, fillcolor=khaki];␊ + "space_bridgeManager" [label="bridgeManager", shape=house, style=filled, fillcolor=khaki];␊ + "space_provisionBridgeManager" [label="provisionBridgeManager", shape=house, style=filled, fillcolor=khaki];␊ + "space_provisionWalletBridgeManager" [label="provisionWalletBridgeManager", shape=house, style=filled, fillcolor=khaki];␊ + "space_walletBridgeManager" [label="walletBridgeManager", shape=house, style=filled, fillcolor=khaki];␊ + "space_chainStorage" [label="chainStorage", shape=house, style=filled, fillcolor=khaki];␊ + "space_chainStorage" [label="chainStorage", shape=house, style=filled, fillcolor=khaki];␊ + "space_bridgeManager" [label="bridgeManager", shape=house, style=filled, fillcolor=khaki];␊ + "space_provisionBridgeManager" [label="provisionBridgeManager", shape=house, style=filled, fillcolor=khaki];␊ + "space_provisionWalletBridgeManager" [label="provisionWalletBridgeManager", shape=house, style=filled, fillcolor=khaki];␊ + "space_bridgeManager" [label="bridgeManager", shape=house, style=filled, fillcolor=khaki];␊ + }␊ + subgraph cluster_BLD {␊ + label = "BLD"␊ + "issuer_BLD" [label="BLD", shape=trapezium, style=filled, fillcolor=chocolate];␊ + "brand_BLD" [label="BLD", shape=Mcircle, style=filled, fillcolor=chocolate2];␊ + }␊ + subgraph cluster_timer {␊ + label = "timer"␊ + "vats_timer" [label="timer", shape=doubleoctagon, style=filled, fillcolor=tomato];␊ + "space_chainTimerService" [label="chainTimerService", shape=house, style=filled, fillcolor=khaki];␊ + "home_chainTimerService" [label="chainTimerService", shape=folder];␊ + }␊ + subgraph cluster_comms {␊ + label = "comms"␊ + "vats_comms" [label="comms", shape=doubleoctagon, style=filled, fillcolor=tomato];␊ + }␊ + subgraph cluster_vattp {␊ + label = "vattp"␊ + "vats_vattp" [label="vattp", shape=doubleoctagon, style=filled, fillcolor=tomato];␊ + }␊ + subgraph cluster_network {␊ + label = "network"␊ + "space_networkVat" [label="networkVat", shape=house, style=filled, fillcolor=khaki];␊ + }␊ + "makeOracleBrands" [label="makeOracleBrands"];␊ + "oracleBrand_USD" [label="USD"];␊ + "startPriceAuthority" [label="startPriceAuthority"];␊ + "space_priceAuthorityVat" [label="priceAuthorityVat", shape=house, style=filled, fillcolor=khaki];␊ + "space_priceAuthority" [label="priceAuthority", shape=house, style=filled, fillcolor=khaki];␊ + "space_priceAuthorityAdmin" [label="priceAuthorityAdmin", shape=house, style=filled, fillcolor=khaki];␊ + "makeVatsFromBundles" [label="makeVatsFromBundles"];␊ + "vats_vatAdmin" [label="vatAdmin", shape=doubleoctagon, style=filled, fillcolor=tomato];␊ + "devices_vatAdmin" [label="vatAdmin", shape=box, style=filled, fillcolor=gold];␊ + "space_vatAdminSvc" [label="vatAdminSvc", shape=house, style=filled, fillcolor=khaki];␊ + "space_loadVat" [label="loadVat", shape=house, style=filled, fillcolor=khaki];␊ + "space_loadCriticalVat" [label="loadCriticalVat", shape=house, style=filled, fillcolor=khaki];␊ + "space_vatStore" [label="vatStore", shape=house, style=filled, fillcolor=khaki];␊ + "buildZoe" [label="buildZoe"];␊ + "space_zoe" [label="zoe", shape=house, style=filled, fillcolor=khaki];␊ + "space_feeMintAccess" [label="feeMintAccess", shape=house, style=filled, fillcolor=khaki];␊ + "space_vatAdminSvc" [label="vatAdminSvc", shape=house, style=filled, fillcolor=khaki];␊ + "issuer_Invitation" [label="Invitation", shape=trapezium, style=filled, fillcolor=chocolate];␊ + "brand_Invitation" [label="Invitation", shape=Mcircle, style=filled, fillcolor=chocolate2];␊ + "makeBoard" [label="makeBoard"];␊ + "space_board" [label="board", shape=house, style=filled, fillcolor=khaki];␊ + "makeAddressNameHubs" [label="makeAddressNameHubs"];␊ + "space_namesByAddress" [label="namesByAddress", shape=house, style=filled, fillcolor=khaki];␊ + "space_namesByAddressAdmin" [label="namesByAddressAdmin", shape=house, style=filled, fillcolor=khaki];␊ + "home_myAddressNameAdmin" [label="myAddressNameAdmin", shape=folder];␊ + "home" [label="home"];␊ + "makeClientBanks" [label="makeClientBanks"];␊ + "space_namesByAddressAdmin" [label="namesByAddressAdmin", shape=house, style=filled, fillcolor=khaki];␊ + "space_bankManager" [label="bankManager", shape=house, style=filled, fillcolor=khaki];␊ + "space_walletFactoryStartResult" [label="walletFactoryStartResult", shape=house, style=filled, fillcolor=khaki];␊ + "home_bank" [label="bank", shape=folder];␊ + "home" [label="home"];␊ + "installBootContracts" [label="installBootContracts"];␊ + "space_zoe" [label="zoe", shape=house, style=filled, fillcolor=khaki];␊ + "space_vatAdminSvc" [label="vatAdminSvc", shape=house, style=filled, fillcolor=khaki];␊ + "installation_centralSupply" [label="centralSupply", shape=cylinder];␊ + "installation_mintHolder" [label="mintHolder", shape=cylinder];␊ + "mintInitialSupply" [label="mintInitialSupply"];␊ + "space_initialSupply" [label="initialSupply", shape=house, style=filled, fillcolor=khaki];␊ + "space_feeMintAccess" [label="feeMintAccess", shape=house, style=filled, fillcolor=khaki];␊ + "space_zoe" [label="zoe", shape=house, style=filled, fillcolor=khaki];␊ + "installation_centralSupply" [label="centralSupply", shape=cylinder];␊ + "addBankAssets" [label="addBankAssets"];␊ + "space_bankManager" [label="bankManager", shape=house, style=filled, fillcolor=khaki];␊ + "space_bldIssuerKit" [label="bldIssuerKit", shape=house, style=filled, fillcolor=khaki];␊ + "space_agoricNamesAdmin" [label="agoricNamesAdmin", shape=house, style=filled, fillcolor=khaki];␊ + "space_initialSupply" [label="initialSupply", shape=house, style=filled, fillcolor=khaki];␊ + "space_bridgeManager" [label="bridgeManager", shape=house, style=filled, fillcolor=khaki];␊ + "space_zoe" [label="zoe", shape=house, style=filled, fillcolor=khaki];␊ + "issuer_BLD" [label="BLD", shape=trapezium, style=filled, fillcolor=chocolate];␊ + "issuer_IST" [label="IST", shape=trapezium, style=filled, fillcolor=chocolate];␊ + "brand_BLD" [label="BLD", shape=Mcircle, style=filled, fillcolor=chocolate2];␊ + "brand_IST" [label="IST", shape=Mcircle, style=filled, fillcolor=chocolate2];␊ + "installation_centralSupply" [label="centralSupply", shape=cylinder];␊ + "installation_mintHolder" [label="mintHolder", shape=cylinder];␊ + "makeBridgeManager" [label="makeBridgeManager"];␊ + "devices_bridge" [label="bridge", shape=box, style=filled, fillcolor=gold];␊ + "space_bridgeManager" [label="bridgeManager", shape=house, style=filled, fillcolor=khaki];␊ + "space_provisionBridgeManager" [label="provisionBridgeManager", shape=house, style=filled, fillcolor=khaki];␊ + "space_provisionWalletBridgeManager" [label="provisionWalletBridgeManager", shape=house, style=filled, fillcolor=khaki];␊ + "space_walletBridgeManager" [label="walletBridgeManager", shape=house, style=filled, fillcolor=khaki];␊ + "startTimerService" [label="startTimerService"];␊ + "vats_timer" [label="timer", shape=doubleoctagon, style=filled, fillcolor=tomato];␊ + "devices_timer" [label="timer", shape=box, style=filled, fillcolor=gold];␊ + "space_chainTimerService" [label="chainTimerService", shape=house, style=filled, fillcolor=khaki];␊ + "home_chainTimerService" [label="chainTimerService", shape=folder];␊ + "home" [label="home"];␊ + "makeChainStorage" [label="makeChainStorage"];␊ + "space_chainStorage" [label="chainStorage", shape=house, style=filled, fillcolor=khaki];␊ + "space_bridgeManager" [label="bridgeManager", shape=house, style=filled, fillcolor=khaki];␊ + "publishAgoricNames" [label="publishAgoricNames"];␊ + "space_agoricNamesAdmin" [label="agoricNamesAdmin", shape=house, style=filled, fillcolor=khaki];␊ + "space_board" [label="board", shape=house, style=filled, fillcolor=khaki];␊ + "space_chainStorage" [label="chainStorage", shape=house, style=filled, fillcolor=khaki];␊ + "makeProvisioner" [label="makeProvisioner"];␊ + "vats_comms" [label="comms", shape=doubleoctagon, style=filled, fillcolor=tomato];␊ + "vats_vattp" [label="vattp", shape=doubleoctagon, style=filled, fillcolor=tomato];␊ + "space_provisioning" [label="provisioning", shape=house, style=filled, fillcolor=khaki];␊ + "space_clientCreator" [label="clientCreator", shape=house, style=filled, fillcolor=khaki];␊ + "bridgeProvisioner" [label="bridgeProvisioner"];␊ + "space_provisioning" [label="provisioning", shape=house, style=filled, fillcolor=khaki];␊ + "space_bridgeManager" [label="bridgeManager", shape=house, style=filled, fillcolor=khaki];␊ + "space_provisionBridgeManager" [label="provisionBridgeManager", shape=house, style=filled, fillcolor=khaki];␊ + "space_provisionWalletBridgeManager" [label="provisionWalletBridgeManager", shape=house, style=filled, fillcolor=khaki];␊ + "setupClientManager" [label="setupClientManager"];␊ + "space_client" [label="client", shape=house, style=filled, fillcolor=khaki];␊ + "space_clientCreator" [label="clientCreator", shape=house, style=filled, fillcolor=khaki];␊ + "setupNetworkProtocols" [label="setupNetworkProtocols"];␊ + "space_networkVat" [label="networkVat", shape=house, style=filled, fillcolor=khaki];␊ + "space_bridgeManager" [label="bridgeManager", shape=house, style=filled, fillcolor=khaki];␊ + "space_zoe" [label="zoe", shape=house, style=filled, fillcolor=khaki];␊ + "space_provisioning" [label="provisioning", shape=house, style=filled, fillcolor=khaki];␊ + "connectChainFaucet" [label="connectChainFaucet"];␊ + "home_faucet" [label="faucet", shape=folder];␊ + "home" [label="home"];␊ + "makeOracleBrands" -> "oracleBrand_USD" []␊ + "startPriceAuthority" -> "space_priceAuthorityVat" []␊ + "startPriceAuthority" -> "space_priceAuthority" []␊ + "startPriceAuthority" -> "space_priceAuthorityAdmin" []␊ + "makeVatsFromBundles" -> "vats_vatAdmin" [dir=back]␊ + "makeVatsFromBundles" -> "devices_vatAdmin" [dir=back]␊ + "makeVatsFromBundles" -> "space_vatAdminSvc" []␊ + "makeVatsFromBundles" -> "space_loadVat" []␊ + "makeVatsFromBundles" -> "space_loadCriticalVat" []␊ + "makeVatsFromBundles" -> "space_vatStore" []␊ + "buildZoe" -> "space_zoe" []␊ + "buildZoe" -> "space_feeMintAccess" []␊ + "buildZoe" -> "space_vatAdminSvc" [dir=back]␊ + "buildZoe" -> "issuer_Invitation" []␊ + "buildZoe" -> "brand_Invitation" []␊ + "makeBoard" -> "space_board" []␊ + "makeAddressNameHubs" -> "space_namesByAddress" []␊ + "makeAddressNameHubs" -> "space_namesByAddressAdmin" []␊ + "makeAddressNameHubs" -> "home_myAddressNameAdmin" []␊ + "home" -> "home_myAddressNameAdmin" []␊ + "home" -> "home_bank" []␊ + "home" -> "home_chainTimerService" []␊ + "home" -> "home_faucet" []␊ + "makeClientBanks" -> "space_namesByAddressAdmin" [dir=back]␊ + "makeClientBanks" -> "space_bankManager" [dir=back]␊ + "makeClientBanks" -> "space_walletFactoryStartResult" [dir=back]␊ + "makeClientBanks" -> "home_bank" []␊ + "installBootContracts" -> "space_zoe" [dir=back]␊ + "installBootContracts" -> "space_vatAdminSvc" [dir=back]␊ + "installBootContracts" -> "installation_centralSupply" []␊ + "installBootContracts" -> "installation_mintHolder" []␊ + "mintInitialSupply" -> "space_initialSupply" []␊ + "mintInitialSupply" -> "space_feeMintAccess" [dir=back]␊ + "mintInitialSupply" -> "space_zoe" [dir=back]␊ + "mintInitialSupply" -> "installation_centralSupply" [dir=back]␊ + "addBankAssets" -> "space_bankManager" []␊ + "addBankAssets" -> "space_bldIssuerKit" []␊ + "addBankAssets" -> "space_agoricNamesAdmin" [dir=back]␊ + "addBankAssets" -> "space_initialSupply" [dir=back]␊ + "addBankAssets" -> "space_bridgeManager" [dir=back]␊ + "addBankAssets" -> "space_zoe" [dir=back]␊ + "addBankAssets" -> "issuer_BLD" []␊ + "addBankAssets" -> "issuer_IST" []␊ + "addBankAssets" -> "brand_BLD" []␊ + "addBankAssets" -> "brand_IST" []␊ + "addBankAssets" -> "installation_centralSupply" [dir=back]␊ + "addBankAssets" -> "installation_mintHolder" [dir=back]␊ + "makeBridgeManager" -> "devices_bridge" [dir=back]␊ + "makeBridgeManager" -> "space_bridgeManager" []␊ + "makeBridgeManager" -> "space_provisionBridgeManager" []␊ + "makeBridgeManager" -> "space_provisionWalletBridgeManager" []␊ + "makeBridgeManager" -> "space_walletBridgeManager" []␊ + "startTimerService" -> "vats_timer" [dir=back]␊ + "startTimerService" -> "devices_timer" [dir=back]␊ + "startTimerService" -> "space_chainTimerService" []␊ + "startTimerService" -> "home_chainTimerService" []␊ + "makeChainStorage" -> "space_chainStorage" []␊ + "makeChainStorage" -> "space_bridgeManager" [dir=back]␊ + "publishAgoricNames" -> "space_agoricNamesAdmin" [dir=back]␊ + "publishAgoricNames" -> "space_board" [dir=back]␊ + "publishAgoricNames" -> "space_chainStorage" [dir=back]␊ + "makeProvisioner" -> "vats_comms" [dir=back]␊ + "makeProvisioner" -> "vats_vattp" [dir=back]␊ + "makeProvisioner" -> "space_provisioning" []␊ + "makeProvisioner" -> "space_clientCreator" [dir=back]␊ + "bridgeProvisioner" -> "space_provisioning" [dir=back]␊ + "bridgeProvisioner" -> "space_bridgeManager" [dir=back]␊ + "bridgeProvisioner" -> "space_provisionBridgeManager" [dir=back]␊ + "bridgeProvisioner" -> "space_provisionWalletBridgeManager" [dir=back]␊ + "setupClientManager" -> "space_client" []␊ + "setupClientManager" -> "space_clientCreator" []␊ + "setupNetworkProtocols" -> "space_networkVat" []␊ + "setupNetworkProtocols" -> "space_bridgeManager" [dir=back]␊ + "setupNetworkProtocols" -> "space_zoe" [dir=back]␊ + "setupNetworkProtocols" -> "space_provisioning" [dir=back]␊ + "connectChainFaucet" -> "home_faucet" []␊ + }␊ + ` diff --git a/packages/vats/test/snapshots/test-boot-config.js.snap b/packages/vats/test/snapshots/test-boot-config.js.snap new file mode 100644 index 00000000000..3836ef7c322 Binary files /dev/null and b/packages/vats/test/snapshots/test-boot-config.js.snap differ diff --git a/packages/vats/test/test-boot-config.js b/packages/vats/test/test-boot-config.js index ed89cad955f..1261c7cd5f0 100644 --- a/packages/vats/test/test-boot-config.js +++ b/packages/vats/test/test-boot-config.js @@ -1,51 +1,214 @@ // @ts-check -import { test } from '@agoric/swingset-vat/tools/prepare-test-env-ava.js'; +import { test as anyTest } from '@agoric/swingset-vat/tools/prepare-test-env-ava.js'; +import { spawn as ambientSpawn } from 'child_process'; import { promises as fsPromises } from 'fs'; import path from 'path'; import { mustMatch } from '@agoric/store'; -import { shape as ssShape } from '@agoric/swingset-vat'; +import { loadSwingsetConfigFile, shape as ssShape } from '@agoric/swingset-vat'; +import { makeNodeBundleCache } from '@agoric/swingset-vat/tools/bundleTool.js'; +import { extractCoreProposalBundles } from '@agoric/deploy-script-support/src/extract-proposal.js'; import { ParametersShape as BootParametersShape } from '../src/core/boot-psm.js'; -const CONFIG_FILES = [ - 'decentral-core-config.json', - 'decentral-demo-config.json', - 'decentral-devnet-config.json', +/** @type {import('ava').TestFn>>} */ +const test = anyTest; + +const PROD_CONFIG_FILES = [ 'decentral-main-psm-config.json', 'decentral-psm-config.json', - 'decentral-test-psm-config.json', 'decentral-test-vaults-config.json', + 'decentral-test-psm-config.json', +]; + +const CONFIG_FILES = [ + 'decentral-core-config.json', // TODO: remove mints from core-config + 'decentral-demo-config.json', + 'decentral-devnet-config.json', + ...PROD_CONFIG_FILES, +]; + +const NON_UPGRADEABLE_VATS = [ + // TODO(#6687): enforce vat-network + // IDEA: move vat-network to a CoreEval proposal? + // 'vat-network', + // 'vat-ibc', + // 'pegasus', + // TODO(#6687): prune centralSupply from prod config + // 'centralSupply', + 'mints', + 'sharing', ]; /** - * @typedef {{ - * asset: (...ps: string[]) => Promise, - * }} Context - * @typedef {import('ava').ExecutionContext } TestCtx + * @param {string} bin + * @param {{ spawn: typeof import('child_process').spawn }} io */ +export const pspawn = + (bin, { spawn }) => + (args = [], opts = {}) => { + /** @type {ReturnType | undefined } */ + let child; + const exit = new Promise((resolve, reject) => { + // console.debug('spawn', bin, args, { cwd: makefileDir, ...opts }); + child = spawn(bin, args, opts); + child.addListener('exit', code => { + if (code !== 0) { + reject(Error(`exit ${code} from: ${bin} ${args}`)); + return; + } + resolve(0); + }); + }); + return { child: child || assert.fail(), exit }; + }; -// NOTE: confine ambient authority to test.before -test.before(t => { +// #region NOTE: confine ambient authority to test.before +const makeTestContext = async () => { const pathname = new URL(import.meta.url).pathname; const dirname = path.dirname(pathname); - const asset = (...ps) => - fsPromises.readFile(path.join(dirname, ...ps), 'utf-8'); - t.context = { asset }; + const pathResolve = (...ps) => path.join(dirname, ...ps); + const asset = (...ps) => fsPromises.readFile(pathResolve(...ps), 'utf-8'); + + const cacheDir = pathResolve('..', 'bundles'); + const bundleCache = await makeNodeBundleCache(cacheDir, {}, s => import(s)); + + const vizTool = pathResolve('..', 'tools', 'authorityViz.js'); + const runViz = pspawn(vizTool, { spawn: ambientSpawn }); + + return { + asset, + bundleCache, + cacheDir, + pathResolve, + basename: path.basename, + runViz, + }; +}; + +test.before(async t => { + t.context = await makeTestContext(); }); +// #endregion -test('Bootstrap SwingSet config file syntax', /** @param {TestCtx} t */ async t => { +test('Bootstrap SwingSet config file syntax', async t => { const { asset } = t.context; await Promise.all( CONFIG_FILES.map(async f => { const txt = await asset('..', f); const config = harden(JSON.parse(txt)); - await t.notThrows(() => mustMatch(config, ssShape.SwingSetConfig), f); + t.notThrows(() => mustMatch(config, ssShape.SwingSetConfig), f); const parameters = config?.vats?.bootstrap?.parameters; t.log('syntax check:', f, parameters ? 'and parameters' : ''); - (await parameters) && + parameters && t.notThrows(() => mustMatch(parameters, BootParametersShape), f); }), ); }); + +const noLog = () => {}; + +const hashCode = s => { + let hash = 0; + let i; + let chr; + if (s.length === 0) return hash; + for (i = 0; i < s.length; i += 1) { + chr = s.charCodeAt(i); + // eslint-disable-next-line no-bitwise + hash = (hash << 5) - hash + chr; + // eslint-disable-next-line no-bitwise + hash |= 0; // Convert to 32bit integer + } + return Math.abs(hash); +}; + +const checkBundle = async (t, sourceSpec, seen, name, configSpec) => { + const { bundleCache, basename } = t.context; + + const targetName = `${hashCode(sourceSpec)}-${basename(sourceSpec, '.js')}`; + + // t.log('checking bundle path', name, spec.sourceSpec); + for (const vatName of NON_UPGRADEABLE_VATS) { + if (targetName.includes(vatName)) { + t.fail(`${configSpec} bundle ${sourceSpec} not upgradeable`); + } + } + + if (!seen.has(targetName)) { + seen.add(targetName); + + t.log(configSpec, ': check bundle:', name, basename(sourceSpec)); + await bundleCache.load(sourceSpec, targetName, noLog); + const meta = await bundleCache.validate(targetName); + t.truthy(meta, name); + + for (const item of meta.contents) { + if (item.relativePath.includes('magic-cookie-test-only')) { + t.fail(`${configSpec} bundle ${name}: ${item.relativePath}`); + } + } + } +}; + +test('no test-only code is in production configs', async t => { + const { pathResolve } = t.context; + const { entries } = Object; + + const seen = new Set(); + + for await (const configSpec of PROD_CONFIG_FILES) { + t.log('checking config', configSpec); + const fullPath = pathResolve('..', configSpec); + const config = await loadSwingsetConfigFile(fullPath); + if (!config) throw t.truthy(config, configSpec); // if/throw refines type + const { bundles } = config; + if (!bundles) throw t.truthy(bundles, configSpec); + + for await (const [name, spec] of entries(bundles)) { + if (!('sourceSpec' in spec)) throw t.fail(); + + await checkBundle(t, spec.sourceSpec, seen, name, configSpec); + } + } +}); + +test('no test-only code is in production proposals', async t => { + const { pathResolve } = t.context; + + const seen = new Set(); + + for await (const configSpec of PROD_CONFIG_FILES) { + t.log('checking config', configSpec); + const getProposals = async () => { + const fullPath = pathResolve('..', configSpec); + const config = await loadSwingsetConfigFile(fullPath); + if (!config) throw t.truthy(config, configSpec); // if/throw refines type + const { coreProposals } = config; + return coreProposals || []; + }; + + const coreProposals = await getProposals(); + const { bundles } = await extractCoreProposalBundles(coreProposals); + + const { entries } = Object; + for await (const [name, spec] of entries(bundles)) { + await checkBundle(t, spec.sourceSpec, seen, name, configSpec); + } + } +}); + +test('bootstrap permit visualization snapshot', async t => { + const { runViz } = t.context; + + const cmd = runViz(['@agoric/vats/decentral-test-vaults-config.json']); + const output = async () => { + const parts = []; + cmd.child.stdout?.on('data', chunk => parts.push(chunk)); + await cmd.exit; + return parts.join(''); + }; + const diagram = await output(); + t.snapshot(diagram); +}); diff --git a/packages/vats/test/test-boot.js b/packages/vats/test/test-boot.js index eb6d3ef7124..e155b0013ad 100644 --- a/packages/vats/test/test-boot.js +++ b/packages/vats/test/test-boot.js @@ -2,14 +2,16 @@ // eslint-disable-next-line import/no-extraneous-dependencies import { test } from '@agoric/swingset-vat/tools/prepare-test-env-ava.js'; import { makeFakeVatAdmin } from '@agoric/zoe/tools/fakeVatAdmin.js'; -import bundleSource from '@endo/bundle-source'; +import bundleSourceAmbient from '@endo/bundle-source'; import { E, passStyleOf } from '@endo/far'; import { eventLoopIteration } from '@agoric/zoe/tools/eventLoopIteration.js'; import { buildRootObject as buildPSMRootObject } from '../src/core/boot-psm.js'; -import { buildRootObject } from '../src/core/boot.js'; +import { buildRootObject } from '../src/core/boot-chain.js'; +import { buildRootObject as buildSimRootObject } from '../src/core/boot-sim.js'; +import { buildRootObject as buildSoloRootObject } from '../src/core/boot-solo.js'; import { bridgeCoreEval } from '../src/core/chain-behaviors.js'; -import { makePromiseSpace } from '../src/core/utils.js'; +import { makePromiseSpace } from '../src/core/promise-space.js'; import { makeMock, @@ -18,53 +20,72 @@ import { mockSwingsetVats, } from '../tools/boot-test-utils.js'; -const argvByRole = { - chain: { - ROLE: 'chain', - }, - 'sim-chain': { - ROLE: 'sim-chain', - FIXME_GCI: 'fake GCI', - hardcodedClientAddresses: ['a1'], - }, - client: { - ROLE: 'client', - FIXME_GCI: 'fake GCI', - hardcodedClientAddresses: ['a1'], - }, +// #region ambient authority limited to test set-up +/** @typedef {import('ava').ExecutionContext>} ECtx */ + +const makeTestContext = () => { + const bundleSource = bundleSourceAmbient; + const loadBundle = async specifier => { + const modulePath = new URL(specifier, import.meta.url).pathname; + /** @type {import('@agoric/swingset-vat').Bundle} */ + const bundle = await bundleSource(modulePath); + return bundle; + }; + + return { loadBundle }; }; -const testRole = (ROLE, governanceActions) => { - test(`test manifest permits: ${ROLE} gov: ${governanceActions}`, async t => { +test.before(t => { + t.context = makeTestContext(); +}); +// #endregion + +/** + * @callback BuildRootObject + * @param {{}} vatPowers + * @param {{}} vatParameters + * @param {import('@agoric/vat-data').Baggage} [baggage] + */ + +/** + * + * @param {string} label + * @param {BuildRootObject} entryPoint + * @param {boolean} doCoreProposals + */ +const testBootstrap = (label, entryPoint, doCoreProposals) => { + const vatParameters = { + argv: { + FIXME_GCI: 'fake GCI', + hardcodedClientAddresses: ['a1'], + }, + }; + + test(`test manifest permits: ${label} gov: ${doCoreProposals}`, async t => { const mock = makeMock(t.log); - const root = buildRootObject( - { D: mockDProxy, logger: t.log }, - { - argv: argvByRole[ROLE], - // @ts-expect-error XXX - governanceActions, - }, - ); + const vatPowers = { D: mockDProxy, logger: t.log }; + const root = entryPoint(vatPowers, vatParameters); const vats = mockSwingsetVats(mock); - const actual = await E(root).bootstrap(vats, mock.devices); - t.deepEqual(actual, undefined); + await t.notThrowsAsync(E(root).bootstrap(vats, mock.devices)); }); }; -testRole('client', false); -testRole('chain', false); -testRole('chain', true); -testRole('sim-chain', false); -testRole('sim-chain', true); +testBootstrap('client', buildSoloRootObject, false); +testBootstrap('chain', buildRootObject, false); +testBootstrap('chain', buildRootObject, true); +testBootstrap('sim', buildSimRootObject, false); +testBootstrap('sim', buildSimRootObject, true); -test('evaluateBundleCap is available to core eval', async t => { +test('evaluateBundleCap is available to core eval', async (/** @type {ECtx} */ t) => { + const { loadBundle } = t.context; + /** @type {undefined | import('../src/types.js').BridgeHandler} */ let handler; - const modulePath = new URL('../src/core/utils.js', import.meta.url).pathname; const { produce, consume } = makePromiseSpace(t.log); const { admin, vatAdminState } = makeFakeVatAdmin(); const vatPowers = vatAdminState.getVatPowers(); const prepare = async () => { - const bundle = await bundleSource(modulePath); + const bundle = await loadBundle('../src/core/utils.js'); + if (bundle.moduleFormat !== 'endoZipBase64') throw t.fail(); const bundleID = bundle.endoZipBase64Sha512; vatAdminState.installBundle(bundleID, bundle); const bridgeManager = { @@ -81,7 +102,7 @@ test('evaluateBundleCap is available to core eval', async t => { // @ts-expect-error await bridgeCoreEval({ vatPowers, produce, consume }); - t.truthy(handler); + if (!handler) throw t.fail(); const produceThing = async ({ consume: { vatAdminSvc }, @@ -105,7 +126,6 @@ test('evaluateBundleCap is available to core eval', async t => { }; t.log({ bridgeMessage }); - // @ts-expect-error await E(handler).fromBridge(bridgeMessage); const actual = await consume.thing; @@ -114,14 +134,7 @@ test('evaluateBundleCap is available to core eval', async t => { }); test('bootstrap provides a way to pass items to CORE_EVAL', async t => { - const root = buildRootObject( - { D: mockDProxy, logger: t.log }, - { - argv: argvByRole.chain, - // @ts-expect-error XXX - governanceActions: false, - }, - ); + const root = buildRootObject({ D: mockDProxy, logger: t.log }, {}); await E(root).produceItem('swissArmyKnife', [1, 2, 3]); t.deepEqual(await E(root).consumeItem('swissArmyKnife'), [1, 2, 3]); diff --git a/packages/vats/test/test-clientBundle.js b/packages/vats/test/test-clientBundle.js index 5625e138fb9..8f0d83aa718 100644 --- a/packages/vats/test/test-clientBundle.js +++ b/packages/vats/test/test-clientBundle.js @@ -7,13 +7,11 @@ import { E, Far } from '@endo/far'; import { makeZoeKit } from '@agoric/zoe'; import { makeIssuerKit } from '@agoric/ertp'; -import { - connectFaucet, - showAmount, -} from '@agoric/inter-protocol/src/proposals/demoIssuers.js'; import { makeScalarBigMapStore } from '@agoric/vat-data'; +import { connectFaucet, showAmount } from '../src/core/demoIssuers.js'; import { setupClientManager } from '../src/core/chain-behaviors.js'; -import { makeAgoricNamesAccess, makePromiseSpace } from '../src/core/utils.js'; +import { makeAgoricNamesAccess } from '../src/core/utils.js'; +import { makePromiseSpace } from '../src/core/promise-space.js'; import { buildRootObject as mintsRoot } from '../src/vat-mints.js'; import { buildRootObject as boardRoot } from '../src/vat-board.js'; import { @@ -145,7 +143,7 @@ test('connectFaucet produces payments', async t => { // t.deepEqual(Object.keys(userBundle), '@@todo'); - /** @type { import('@agoric/inter-protocol/src/proposals/demoIssuers.js').UserPaymentRecord[] } */ + /** @type { import('../src/core/demoIssuers.js').UserPaymentRecord[] } */ const pmts = await E(userBundle.faucet).tapFaucet(); const detail = await Promise.all( @@ -157,6 +155,6 @@ test('connectFaucet produces payments', async t => { ); t.deepEqual(detail, [ ['Oracle fee', '51 LINK'], - ['USD Coin', '1_323 USDC'], + ['DAI', '1_323 DAI'], ]); }); diff --git a/packages/inter-protocol/test/test-demoAMM.js b/packages/vats/test/test-demoAMM.js similarity index 93% rename from packages/inter-protocol/test/test-demoAMM.js rename to packages/vats/test/test-demoAMM.js index 1f9c35346e5..a798e5dc843 100644 --- a/packages/inter-protocol/test/test-demoAMM.js +++ b/packages/vats/test/test-demoAMM.js @@ -1,7 +1,7 @@ // eslint-disable-next-line import/no-extraneous-dependencies import { test } from '@agoric/swingset-vat/tools/prepare-test-env-ava.js'; import { AmountMath, makeIssuerKit } from '@agoric/ertp'; -import { Stable } from '@agoric/vats/src/tokens.js'; +import { Stable } from '../src/tokens.js'; import { AMMDemoState, @@ -12,17 +12,19 @@ import { showAmount, showBrand, splitAllCentralPayments, -} from '../src/proposals/demoIssuers.js'; +} from '../src/core/demoIssuers.js'; /** @param { bigint } n */ const showIST = n => `${decimal(n, 6)} IST`; +// TODO: prune showIST formatting utility test('uist -> IST formatting test utility', t => { t.is(showIST(123456789n), '123.456789 IST', 'IST decimal point'); t.is(showIST(1234567890n), '1_234.56789 IST', 'thousands separators'); t.is(showIST(3286010000000000n), '3_286_010_000 IST', 'regression 1'); }); +// TODO: prune splitAllCentralPayments AMM utility? test('splitAllCentralPayments: count entries, spot check', async t => { const central = makeIssuerKit( Stable.symbol, @@ -41,7 +43,7 @@ test('splitAllCentralPayments: count entries, spot check', async t => { // t.log(actual); t.is(actual.ATOM.amount.brand, central.brand); t.deepEqual(showAmount(actual.ATOM.amount), '33_280_000 IST'); - t.deepEqual(Object.keys(actual), ['BLD', 'ATOM', 'WETH', 'LINK', 'USDC']); + t.deepEqual(Object.keys(actual), ['BLD', 'ATOM', 'WETH', 'LINK', 'DAI']); }); test('poolRates: spot check WETH', t => { diff --git a/packages/vats/test/test-name-hub-published.js b/packages/vats/test/test-name-hub-published.js index 32439cf8c3f..c0d3cd21cc8 100644 --- a/packages/vats/test/test-name-hub-published.js +++ b/packages/vats/test/test-name-hub-published.js @@ -4,7 +4,8 @@ import { test } from '@agoric/swingset-vat/tools/prepare-test-env-ava.js'; import { makeMockChainStorageRoot } from '@agoric/inter-protocol/test/supports.js'; import { makeHandle } from '@agoric/zoe/src/makeHandle.js'; import { eventLoopIteration } from '@agoric/zoe/tools/eventLoopIteration.js'; -import { makeAgoricNamesAccess, makePromiseSpace } from '../src/core/utils.js'; +import { makeAgoricNamesAccess } from '../src/core/utils.js'; +import { makePromiseSpace } from '../src/core/promise-space.js'; import { publishAgoricNames, setupClientManager, diff --git a/packages/vats/test/test-promise-space.js b/packages/vats/test/test-promise-space.js index 10a5baaf667..d671483715d 100644 --- a/packages/vats/test/test-promise-space.js +++ b/packages/vats/test/test-promise-space.js @@ -1,7 +1,7 @@ // @ts-check import { test } from '@agoric/swingset-vat/tools/prepare-test-env-ava.js'; -import { makePromiseSpace } from '../src/core/utils.js'; +import { makePromiseSpace } from '../src/core/promise-space.js'; test('makePromiseSpace', async t => { const { produce, consume } = makePromiseSpace(); diff --git a/packages/vats/test/test-provisionPool.js b/packages/vats/test/test-provisionPool.js index 38744f79a6f..19c43c2965d 100644 --- a/packages/vats/test/test-provisionPool.js +++ b/packages/vats/test/test-provisionPool.js @@ -17,7 +17,7 @@ import { eventLoopIteration } from '@agoric/zoe/tools/eventLoopIteration.js'; import { E } from '@endo/far'; import path from 'path'; import centralSupplyBundle from '../bundles/bundle-centralSupply.js'; -import { PowerFlags } from '../src/core/basic-behaviors.js'; +import { PowerFlags } from '../src/walletFlags.js'; import { makeBoard } from '../src/lib-board.js'; import { makeNameHubKit } from '../src/nameHub.js'; import { makeBridgeProvisionTool } from '../src/provisionPool.js'; diff --git a/packages/vats/test/test-vat-bank.js b/packages/vats/test/test-vat-bank.js index 738dbd3baa5..c60202eaada 100644 --- a/packages/vats/test/test-vat-bank.js +++ b/packages/vats/test/test-vat-bank.js @@ -13,7 +13,8 @@ import { addBankAssets, installBootContracts, } from '../src/core/basic-behaviors.js'; -import { makeAgoricNamesAccess, makePromiseSpace } from '../src/core/utils.js'; +import { makeAgoricNamesAccess } from '../src/core/utils.js'; +import { makePromiseSpace } from '../src/core/promise-space.js'; import { makePopulatedFakeVatAdmin } from '../tools/boot-test-utils.js'; test('communication', async t => { diff --git a/packages/vats/test/upgrading/test-upgrade-vats.js b/packages/vats/test/upgrading/test-upgrade-vats.js index ee6fb9ec8fa..5137732aef9 100644 --- a/packages/vats/test/upgrading/test-upgrade-vats.js +++ b/packages/vats/test/upgrading/test-upgrade-vats.js @@ -2,14 +2,24 @@ import { test as anyTest } from '@agoric/swingset-vat/tools/prepare-test-env-ava.js'; import { buildVatController } from '@agoric/swingset-vat'; -import { kunser } from '@agoric/swingset-vat/src/lib/kmarshal.js'; +import { makeRunUtils } from '../bootstrapTests/supports.js'; + +const resolveAssetModule = specifier => + new URL(specifier, import.meta.url).pathname; +const bundleSpecs = { + relay: { + // XXX cross-package asset module + sourceSpec: resolveAssetModule('../../../SwingSet/test/bootstrap-relay.js'), + }, + board: { sourceSpec: resolveAssetModule('../../src/vat-board.js') }, + chain: { sourceSpec: resolveAssetModule('../../src/core/boot-chain.js') }, +}; /** @type {import('ava').TestFn>>} */ const test = anyTest; -const makeTestContext = async metaUrl => { - const bfile = name => new URL(name, metaUrl).pathname; - return { bfile }; +const makeTestContext = async () => { + return {}; }; /** @@ -20,9 +30,7 @@ test.before(async t => { t.context = await makeTestContext(import.meta.url); }); -test('upgrade vat-board', async t => { - const { bfile } = t.context; - +const makeScenario = async (t, bundles) => { /** @type {SwingSetConfig} */ const config = { includeDevDependencies: true, // for vat-data @@ -30,54 +38,67 @@ test('upgrade vat-board', async t => { bootstrap: 'bootstrap', defaultReapInterval: 'never', vats: { - bootstrap: { - sourceSpec: bfile('../../../SwingSet/test/bootstrap-relay.js'), - }, - }, - bundles: { - board: { sourceSpec: bfile('../../src/vat-board.js') }, + bootstrap: bundleSpecs.relay, }, + bundles, }; const c = await buildVatController(config); t.teardown(c.shutdown); c.pinVatRoot('bootstrap'); - await c.run(); + const runUtils = makeRunUtils(c, t.log); - const run = async (method, args = []) => { - assert(Array.isArray(args)); - const kpid = c.queueToVatRoot('bootstrap', method, args); - await c.run(); - const status = c.kpStatus(kpid); - if (status === 'fulfilled') { - const result = c.kpResolution(kpid); - return kunser(result); - } - assert(status === 'rejected'); - const err = c.kpResolution(kpid); - throw kunser(err); + return runUtils; +}; + +test('upgrade vat-board', async t => { + const bundles = { + board: bundleSpecs.board, }; - const messageVat = (name, methodName, args) => - run('messageVat', [{ name, methodName, args }]); - const messageObject = (presence, methodName, args) => - run('messageVatObject', [{ presence, methodName, args }]); + const { EV } = await makeScenario(t, bundles); t.log('create initial version'); const boardVatConfig = { name: 'board', bundleCapName: 'board', }; - await run('createVat', [boardVatConfig]); - const board = await messageVat('board', 'getBoard', []); - const thing = await run('makeSimpleRemotable', ['Thing', {}]); - const thingId = await messageObject(board, 'getId', [thing]); + await EV.vat('bootstrap').createVat(boardVatConfig); + const board = await EV.vat('board').getBoard(); + const thing = await EV.vat('bootstrap').makeSimpleRemotable('Thing', {}); + const thingId = await EV(board).getId(thing); t.regex(thingId, /^board0[0-9]+$/); t.log('now perform the null upgrade'); - const { incarnationNumber } = await run('upgradeVat', [boardVatConfig]); + const { incarnationNumber } = await EV.vat('bootstrap').upgradeVat( + boardVatConfig, + ); t.is(incarnationNumber, 2, 'Board vat must be upgraded'); - const board2 = await messageVat('board', 'getBoard', []); + const board2 = await EV.vat('board').getBoard(); t.is(board2, board, 'must get the same board reference'); - const actualThing = await messageObject(board2, 'getValue', [thingId]); + const actualThing = await EV(board2).getValue(thingId); t.is(actualThing, thing, 'must get original value back'); }); + +test.skip('upgrade bootstrap vat', async t => { + const bundles = { + chain: bundleSpecs.chain, + }; + const { EV } = await makeScenario(t, bundles); + + t.log('create initial version'); + const chainVatConfig = { + name: 'chain', + bundleCapName: 'chain', + }; + await EV.vat('bootstrap').createVat(chainVatConfig); + await EV.vat('chain') + .bootstrap({}, {}) + .catch(problem => t.log('TODO: address problem:', problem)); + + t.log('now perform the null upgrade'); + + const { incarnationNumber } = await EV.vat('bootstrap').upgradeVat( + chainVatConfig, + ); + t.is(incarnationNumber, 2, 'vat must be upgraded'); +}); diff --git a/packages/vats/tools/authorityViz.js b/packages/vats/tools/authorityViz.js new file mode 100755 index 00000000000..fbebcca2ba1 --- /dev/null +++ b/packages/vats/tools/authorityViz.js @@ -0,0 +1,272 @@ +#!/usr/bin/env node +// @ts-check +import '@endo/init'; +import process from 'process'; + +const { entries, keys, values } = Object; + +const logged = label => x => { + console.error(label, x); + return x; +}; + +const styles = { + vatPowers: 'shape=star, style=filled, fillcolor=aqua', + vats: 'shape=doubleoctagon, style=filled, fillcolor=tomato', + vat: 'shape=doubleoctagon, style=filled, fillcolor=tomato3', + devices: 'shape=box, style=filled, fillcolor=gold', + space: 'shape=house, style=filled, fillcolor=khaki', + issuer: 'shape=trapezium, style=filled, fillcolor=chocolate', + brand: 'shape=Mcircle, style=filled, fillcolor=chocolate2', + installation: 'shape=cylinder', + instance: 'shape=component', + home: 'shape=folder', +}; + +/** + * @param {Set} nodes + * @param {Map>} neighbors + * @yields { string } + * @typedef {{ + * id: string, + * cluster?: string, + * label: string, + * style?: string, + * }} GraphNode + */ +function* fmtGraph(nodes, neighbors) { + const q = txt => JSON.stringify(txt.replace(/\./g, '_')); + yield 'digraph G {\n'; + yield 'rankdir = LR;\n'; + const clusters = new Set( + [...nodes].map(({ cluster }) => cluster).filter(c => !!c), + ); + for (const subgraph of [...clusters, undefined]) { + if (subgraph) { + assert.typeof(subgraph, 'string'); + yield `subgraph cluster_${subgraph} {\n`; + yield `label = "${subgraph}"\n`; + } + for (const { id, cluster, label, style } of nodes) { + if (subgraph && cluster !== subgraph) continue; + yield `${q(id)} [label=${q(label)}${style ? `, ${style}` : ''}];\n`; + } + if (subgraph) { + yield `}\n`; + } + } + for (const [src, arcs] of neighbors.entries()) { + for (const { id, style } of arcs) { + yield `${q(src)} -> ${q(id)} [${style}]\n`; + } + } + yield '}\n'; +} + +/** + * + * @param {Record} manifest + * @typedef { true | { + * vatParameters?: Record, + * vatPowers?: Record + * vats?: Record + * devices?: Record + * home?: PowerSpace, + * issuer?: PowerSpace, + * brand?: PowerSpace, + * oracleBrand?: PowerSpace, + * installation?: PowerSpace, + * instance?: PowerSpace, + * } & PowerSpace } Permit + * @typedef {{produce?: Record, consume?: Record}} PowerSpace + * @typedef { boolean | VatName } Status + * @typedef { string } VatName + */ +const manifest2graph = manifest => { + /** @type { Set } */ + const nodes = new Set(); + const neighbors = new Map(); + + /** + * @param {string} src + * @param {string} dest + * @param {string} [style] + */ + const addEdge = (src, dest, style = '') => { + logged('addEdge')({ src, dest }); + if (!neighbors.has(src)) { + neighbors.set(src, new Set()); + } + neighbors.get(src).add({ id: dest, style }); + }; + + /** + * @param {string} src + * @param {string} ty + * @param {Permit} item + * @param {boolean} [reverse=false] + */ + const level1 = (src, ty, item, reverse = false) => { + if (!item) return; + for (const [powerName, status] of entries(item)) { + // subsumed by permits for vat:, home:, ... + if ( + [ + 'loadVat', + 'loadCriticalVat', + 'client', + 'agoricNames', + 'nameHubs', + 'nameAdmins', + ].includes(powerName) && + reverse + ) + continue; + if (status) { + let cluster = {}; + if (typeof status !== 'boolean') { + cluster = { cluster: status }; + } + + nodes.add({ + id: `${ty}.${powerName}`, + label: powerName, + style: styles[ty], + ...cluster, + }); + addEdge(src, `${ty}.${powerName}`, reverse ? 'dir=back' : ''); + if (ty === 'home') { + nodes.add({ id: 'home', label: 'home', cluster: 'provisioning' }); + addEdge('home', `${ty}.${powerName}`); + } + } + } + }; + + /** @type {(xs: X[]) => X[]} */ + const uniq = xs => [...new Set(xs)]; + const spaces = uniq( + logged('vals')(values(manifest)).flatMap(permit => + logged('keys?')(keys(permit)).flatMap(name => { + logged('name?')(name); + + if (['produce', 'consume'].includes(name)) return []; + return [name]; + }), + ), + ); + console.error('@@@', { spaces }); + + for (const [fnName, permit] of entries(manifest)) { + if (permit === true) { + console.error('skipping wildcard permit:', fnName); + continue; + } + nodes.add({ id: logged('fn')(fnName), label: fnName }); + + permit.vatPowers && level1(fnName, 'vatPowers', permit.vatPowers, true); + permit.vats && level1(fnName, 'vats', permit.vats, true); + permit.devices && level1(fnName, 'devices', permit.devices, true); + + const doPart = (name, part) => { + if (!part) return; + level1(fnName, name, part.produce || {}); + level1(fnName, name, part.consume || {}, true); + }; + doPart('space', permit); + spaces.forEach(s => doPart(s, permit[s])); + + if ('runBehaviors' in permit) { + throw Error('not impl'); + // nodes.add({ + // id: `runBehaviors`, + // label: `runBehaviors`, + // style: '', + // }); + // addEdge(fnName, `runBehaviors`); + } + } + return { nodes, neighbors }; +}; + +const { Fail, quote: q } = assert; + +/** + * @param {string} specifier + * @param {object} io + * @param {Resolver} io.resolve + * @param {typeof import('fs/promises').readFile} io.readFile + */ +const loadConfig = async (specifier, { resolve, readFile }) => { + const fullPath = await resolve(specifier, import.meta.url).then( + u => new URL(u).pathname, + ); + typeof fullPath === 'string' || Fail`${q(specifier)}`; + const txt = await readFile(fullPath, 'utf-8'); + typeof txt === 'string' || Fail`readFile ${q(fullPath)}`; + return JSON.parse(txt); +}; + +/** + * @param {string[]} args + * @param {object} io + * @param {typeof import('process').stdout} io.stdout + * @param {typeof import('fs/promises')} io.fsp + * @param {{ + * resolve: Resolver, + * url: string, + * load: (specifier: string) => Promise>, + * }} io.meta + * + * @typedef {(specifier: string, parent: string) => Promise} Resolver + */ +const main = async (args, { stdout, fsp, meta }) => { + const [specifier, ...opts] = args; + specifier || Fail`Usage: $0 @agoric/vats/decentral-...json`; + + const config = await loadConfig(specifier, { + resolve: meta.resolve, + readFile: fsp.readFile, + }); + // console.log(config); + const { bootstrap } = config.vats; + + const { MANIFEST } = await meta + .resolve(bootstrap.sourceSpec, meta.url) + .then(p => meta.load(p)); + // console.log('manifest keys:', Object.keys(MANIFEST)); + + const [gov] = ['--gov'].map(opt => opts.includes(opt)); + + if (gov) { + throw Error('not impl'); + /* + const postBoot = sim + ? manifests.SIM_CHAIN_POST_BOOT_MANIFEST + : manifests.CHAIN_POST_BOOT_MANIFEST; + manifest = { ...postBoot, ...manifest }; + */ + } + + const g = manifest2graph(MANIFEST); + for (const chunk of fmtGraph(g.nodes, g.neighbors)) { + stdout.write(chunk); + } +}; + +(async () => { + // eslint-disable-next-line import/no-extraneous-dependencies + Promise.all([import('fs/promises'), import('import-meta-resolve')]).then( + ([fsp, metaResolve]) => { + return main(process.argv.slice(2), { + stdout: process.stdout, + fsp, + meta: { + resolve: metaResolve.resolve, + url: import.meta.url, + load: specifier => import(specifier), + }, + }); + }, + ); +})().catch(console.error); diff --git a/packages/vats/tools/viz.mk b/packages/vats/tools/viz.mk new file mode 100644 index 00000000000..69396a48c58 --- /dev/null +++ b/packages/vats/tools/viz.mk @@ -0,0 +1,17 @@ +# config: tools outsidet the source tree +VIEWER=firefox +DOT=dot + +CONFIG=decentral-test-vaults-config + +SRC=../src/core/chain-behaviors.js ../src/core/basic-behaviors.js + +$(CONFIG).svg: $(CONFIG).dot + $(DOT) -Tsvg $< >$@ || rm $@ + +view: $(CONFIG).svg + $(VIEWER) $< + +$(CONFIG).dot: ../$(CONFIG).json $(SRC) authorityViz.js + node authorityViz.js ../$(CONFIG).json >$@ || rm $@ +