diff --git a/packages/agoric-cli/src/commands/oracle.js b/packages/agoric-cli/src/commands/oracle.js index c0b13a34690..2c458070e06 100644 --- a/packages/agoric-cli/src/commands/oracle.js +++ b/packages/agoric-cli/src/commands/oracle.js @@ -7,6 +7,7 @@ import { Nat } from '@endo/nat'; import { Command } from 'commander'; import * as cp from 'child_process'; import { inspect } from 'util'; +import { instanceNameFor } from '@agoric/inter-protocol/src/proposals/price-feed-proposal.js'; import { normalizeAddressWithOptions } from '../lib/chain.js'; import { getNetworkConfig, makeRpcUtils, storageHelper } from '../lib/rpc.js'; import { @@ -75,7 +76,7 @@ export const makeOracleCommand = (logger, io = {}) => { const utils = await makeRpcUtils({ fetch }); const lookupPriceAggregatorInstance = ([brandIn, brandOut]) => { - const name = `${brandIn}-${brandOut} price feed`; + const name = instanceNameFor(brandIn, brandOut); const instance = utils.agoricNames.instance[name]; if (!instance) { logger.debug('known instances:', utils.agoricNames.instance); diff --git a/packages/inter-protocol/package.json b/packages/inter-protocol/package.json index 9da23e33cd5..7f10aac8141 100644 --- a/packages/inter-protocol/package.json +++ b/packages/inter-protocol/package.json @@ -10,6 +10,7 @@ "scripts": { "build": "yarn build:bundles", "build:bundles": "node ./scripts/build-bundles.js", + "build:add-STARS-proposal": "agoric run scripts/add-STARS.js", "test": "ava", "test:c8": "c8 $C8_OPTIONS ava --config=ava-nesm.config.js", "test:xs": "exit 0", diff --git a/packages/inter-protocol/scripts/add-STARS.js b/packages/inter-protocol/scripts/add-STARS.js new file mode 100644 index 00000000000..8e6b0025bf4 --- /dev/null +++ b/packages/inter-protocol/scripts/add-STARS.js @@ -0,0 +1,43 @@ +import { makeHelpers } from '@agoric/deploy-script-support'; +import { defaultProposalBuilder as vaultProposalBuilder } from './add-collateral-core.js'; +import { defaultProposalBuilder as oraclesProposalBuilder } from './price-feed-core.js'; + +/** @type {import('@agoric/deploy-script-support/src/externalTypes.js').ProposalBuilder} */ +export const starsVaultProposalBuilder = async powers => { + return vaultProposalBuilder(powers, { + interchainAssetOptions: { + // Values for the Stargaze token on Osmosis + denom: + 'ibc/987C17B11ABC2B20019178ACE62929FE9840202CE79498E29FE8E5CB02B7C0A4', + decimalPlaces: 6, + keyword: 'STARS', + oracleBrand: 'STARS', + proposedName: 'STARS', + }, + }); +}; + +/** @type {import('@agoric/deploy-script-support/src/externalTypes.js').ProposalBuilder} */ +export const starsOraclesProposalBuilder = async powers => { + return oraclesProposalBuilder(powers, { + AGORIC_INSTANCE_NAME: `STARS-USD price feed`, + IN_BRAND_LOOKUP: ['agoricNames', 'oracleBrand', 'STARS'], + IN_BRAND_DECIMALS: 6, + OUT_BRAND_LOOKUP: ['agoricNames', 'oracleBrand', 'USD'], + OUT_BRAND_DECIMALS: 4, + oracleAddresses: [ + // copied from decentral-main-vaults-config.json + 'agoric1krunjcqfrf7la48zrvdfeeqtls5r00ep68mzkr', + 'agoric19uscwxdac6cf6z7d5e26e0jm0lgwstc47cpll8', + 'agoric144rrhh4m09mh7aaffhm6xy223ym76gve2x7y78', + 'agoric19d6gnr9fyp6hev4tlrg87zjrzsd5gzr5qlfq2p', + 'agoric1n4fcxsnkxe4gj6e24naec99hzmc4pjfdccy5nj', + ], + }); +}; + +export default async (homeP, endowments) => { + const { writeCoreProposal } = await makeHelpers(homeP, endowments); + await writeCoreProposal('add-STARS', starsVaultProposalBuilder); + await writeCoreProposal('add-STARS-oracles', starsOraclesProposalBuilder); +}; diff --git a/packages/inter-protocol/src/proposals/addAssetToVault.js b/packages/inter-protocol/src/proposals/addAssetToVault.js index 2d983c2a42c..d2a0c2dc527 100644 --- a/packages/inter-protocol/src/proposals/addAssetToVault.js +++ b/packages/inter-protocol/src/proposals/addAssetToVault.js @@ -1,11 +1,12 @@ // @jessie-check import { AmountMath, AssetKind } from '@agoric/ertp'; -import { makeRatio } from '@agoric/zoe/src/contractSupport/index.js'; import { deeplyFulfilledObject } from '@agoric/internal'; import { Stable } from '@agoric/vats/src/tokens.js'; -import { E } from '@endo/far'; +import { makeRatio } from '@agoric/zoe/src/contractSupport/index.js'; import { parseRatio } from '@agoric/zoe/src/contractSupport/ratio.js'; +import { E } from '@endo/far'; +import { instanceNameFor } from './price-feed-proposal.js'; import { reserveThenGetNames } from './utils.js'; export * from './startPSM.js'; @@ -219,6 +220,7 @@ export const addAssetToVault = async ( brand: { consume: { [Stable.symbol]: stableP }, }, + instance: { consume: consumeInstance }, }, { options: { @@ -239,6 +241,11 @@ export const addAssetToVault = async ( [keyword], ); + const oracleInstanceName = instanceNameFor(oracleBrand, 'USD'); + // don't add the collateral offering to vaultFactory until its price feed is available + // eslint-disable-next-line no-restricted-syntax -- allow this computed property + await consumeInstance[oracleInstanceName]; + const stable = await stableP; const vaultFactoryCreator = E.get(vaultFactoryKit).creatorFacet; await E(vaultFactoryCreator).addVaultType(interchainIssuer, oracleBrand, { @@ -321,6 +328,11 @@ export const getManifestForAddAssetToVault = ( brand: { consume: { [Stable.symbol]: true }, }, + instance: { + // allow any instance because the AGORIC_INSTANCE_NAME of + // priceFeedOptions cannot be known statically. + consume: true, + }, }, }, installations: { diff --git a/packages/inter-protocol/src/proposals/price-feed-proposal.js b/packages/inter-protocol/src/proposals/price-feed-proposal.js index 6cd47379d3c..751d3c811c8 100644 --- a/packages/inter-protocol/src/proposals/price-feed-proposal.js +++ b/packages/inter-protocol/src/proposals/price-feed-proposal.js @@ -1,10 +1,10 @@ // @ts-nocheck -- lots of type errors. low prio b/c proposals are like scripts -import { E } from '@endo/far'; +import { makeTracer } from '@agoric/internal'; import { - makeStorageNodeChild, assertPathSegment, + makeStorageNodeChild, } from '@agoric/internal/src/lib-chainStorage.js'; -import { makeTracer } from '@agoric/internal'; +import { E } from '@endo/far'; import { unitAmount } from '@agoric/zoe/src/contractSupport/priceQuote.js'; import { reserveThenDeposit, reserveThenGetNames } from './utils.js'; @@ -18,6 +18,9 @@ const sanitizePathSegment = name => { return candidate; }; +export const instanceNameFor = (inBrandName, outBrandName) => + `${inBrandName}-${outBrandName} price feed`; + /** * @typedef {{ * brandIn?: ERef | undefined>, @@ -95,6 +98,7 @@ export const createPriceFeed = async ( priceAuthorityAdmin, startGovernedUpgradable, }, + instance: { produce: produceInstance }, }, { options: { @@ -160,11 +164,27 @@ export const createPriceFeed = async ( installation: priceAggregator, }); - E(E(agoricNamesAdmin).lookupAdmin('instance')) + // Publish price feed in home.priceAuthority. + const forceReplace = true; + // Make sure this PA is registered before sharing the instance in agoricNames, + // which allows contracts that depend on the registry value to wait for it and + // prevent a race. + await E(priceAuthorityAdmin).registerPriceAuthority( + E(faKit.publicFacet).getPriceAuthority(), + brandIn, + brandOut, + forceReplace, + ); + + await E(E(agoricNamesAdmin).lookupAdmin('instance')) .update(AGORIC_INSTANCE_NAME, faKit.instance) .catch(err => console.error(`🚨 failed to update ${AGORIC_INSTANCE_NAME}`, err), ); + // being after the above awaits means that when this resolves, the consumer + // gets notified that the authority is in the registry and its instance is in + // agoricNames. + produceInstance[AGORIC_INSTANCE_NAME].resolve(faKit.instance); E(E.get(econCharterKit).creatorFacet).addInstance( faKit.instance, @@ -173,15 +193,6 @@ export const createPriceFeed = async ( ); trace('registered', AGORIC_INSTANCE_NAME, faKit.instance); - // Publish price feed in home.priceAuthority. - const forceReplace = true; - void E(priceAuthorityAdmin).registerPriceAuthority( - E(faKit.publicFacet).getPriceAuthority(), - brandIn, - brandOut, - forceReplace, - ); - /** * Initialize a new oracle and send an invitation to administer it. * @@ -231,6 +242,9 @@ export const getManifestForPriceFeed = async ( priceAuthorityAdmin: t, startGovernedUpgradable: t, }, + instance: { + produce: t, + }, }, [ensureOracleBrands.name]: { namedVat: { @@ -242,6 +256,7 @@ export const getManifestForPriceFeed = async ( }, }, installations: { + // ??? will every eval of price-feed-proposal install priceAggregator ? priceAggregator: restoreRef(priceFeedOptions.priceAggregatorRef), }, options: { @@ -302,7 +317,7 @@ export const startPriceFeeds = async ( { options: { priceFeedOptions: { - AGORIC_INSTANCE_NAME: `${inBrandName}-${outBrandName} price feed`, + AGORIC_INSTANCE_NAME: instanceNameFor(inBrandName, outBrandName), contractTerms: { minSubmissionCount: 2, minSubmissionValue: 1, diff --git a/packages/inter-protocol/src/vaultFactory/vaultDirector.js b/packages/inter-protocol/src/vaultFactory/vaultDirector.js index 86aff7c1b11..0562e7ce153 100644 --- a/packages/inter-protocol/src/vaultFactory/vaultDirector.js +++ b/packages/inter-protocol/src/vaultFactory/vaultDirector.js @@ -250,6 +250,7 @@ const prepareVaultDirector = ( metrics: makeRecorderTopic('Vault Factory metrics', metricsKit), }); + /** @param {(vm: VaultManager) => void} fn */ const allManagersDo = fn => { for (const managerIndex of collateralManagers.values()) { const vm = vaultManagers.get(managerIndex).self; @@ -424,6 +425,7 @@ const prepareVaultDirector = ( makeLiquidationWaker() { return makeWaker('liquidationWaker', _timestamp => { + // XXX floating promise allManagersDo(vm => vm.liquidateVaults(auctioneer)); }); }, diff --git a/packages/inter-protocol/src/vaultFactory/vaultManager.js b/packages/inter-protocol/src/vaultFactory/vaultManager.js index 6c41b45ac76..bc998404f1a 100644 --- a/packages/inter-protocol/src/vaultFactory/vaultManager.js +++ b/packages/inter-protocol/src/vaultFactory/vaultManager.js @@ -400,7 +400,7 @@ export const prepareVaultManagerKit = ( // throw. See https://github.com/Agoric/agoric-sdk/issues/4317 void observeNotifier(quoteNotifier, { updateState(value) { - trace('vaultManager got new collateral quote', value); + trace('storing new quote', value.quoteAmount.value); ephemera.storedCollateralQuote = value; }, fail(reason) { @@ -1036,7 +1036,7 @@ export const prepareVaultManagerKit = ( if (!storedCollateralQuote) throw Fail`lockOraclePrices called before a collateral quote was available`; trace( - `lockPrice`, + `lockOraclePrices`, getAmountIn(storedCollateralQuote), getAmountOut(storedCollateralQuote), ); @@ -1046,7 +1046,7 @@ export const prepareVaultManagerKit = ( return storedCollateralQuote; }, /** - * @param {AuctioneerPublicFacet} auctionPF + * @param {ERef} auctionPF */ async liquidateVaults(auctionPF) { const { state, facets } = this; @@ -1060,11 +1060,19 @@ export const prepareVaultManagerKit = ( } = state; trace(collateralBrand, 'considering liquidation'); + if (!lockedQuote) { + // By design, the first cycle of auction may call this before a quote is locked + // because the schedule is global at the vaultDirector level, and if a manager + // starts after the price lock time there's nothing to be done. + // NB: this message should not log repeatedly. + console.error( + 'Skipping liquidation because no quote is locked yet (may happen with new manager)', + ); + return; + } + const { prioritizedVaults } = collateralEphemera(collateralBrand); - assert(factoryPowers && prioritizedVaults && zcf); - lockedQuote || - Fail`Must have locked a quote before liquidating vaults.`; - assert(lockedQuote); // redundant with previous line + prioritizedVaults || Fail`prioritizedVaults missing from ephemera`; const liqMargin = self.getGovernedParams().getLiquidationMargin(); diff --git a/packages/inter-protocol/test/smartWallet/contexts.js b/packages/inter-protocol/test/smartWallet/contexts.js index 35f0d8d4958..f565e32b4f2 100644 --- a/packages/inter-protocol/test/smartWallet/contexts.js +++ b/packages/inter-protocol/test/smartWallet/contexts.js @@ -11,7 +11,10 @@ import { import { heapZone } from '@agoric/zone'; import { E } from '@endo/far'; import path from 'path'; -import { createPriceFeed } from '../../src/proposals/price-feed-proposal.js'; +import { + createPriceFeed, + instanceNameFor, +} from '../../src/proposals/price-feed-proposal.js'; import { withAmountUtils } from '../supports.js'; // referenced by TS @@ -42,7 +45,8 @@ export const makeDefaultTestContext = async (t, makeSpace) => { const bundleCache = await unsafeMakeBundleCache('bundles/'); - const { consume, produce } = await makeSpace(log, bundleCache); + // @ts-expect-error xxx + const { consume, produce, instance } = await makeSpace(log, bundleCache); const { agoricNames, zoe } = consume; await produceDiagnostics({ produce }); @@ -155,12 +159,20 @@ export const makeDefaultTestContext = async (t, makeSpace) => { const paInstallation = E(zoe).install(paBundle); await E(installAdmin).update('priceAggregator', paInstallation); + const mockPriceAuthorityAdmin = /** @type {any} */ ({ + registerPriceAuthority() { + // noop mock + }, + }); + produce.priceAuthorityAdmin.resolve(mockPriceAuthorityAdmin); + await createPriceFeed( - { consume, produce }, + // @ts-expect-error xxx + { consume, produce, instance }, { options: { priceFeedOptions: { - AGORIC_INSTANCE_NAME: `${inBrandName}-${outBrandName} price feed`, + AGORIC_INSTANCE_NAME: instanceNameFor(inBrandName, outBrandName), contractTerms: { minSubmissionCount: 2, minSubmissionValue: 1, diff --git a/packages/inter-protocol/test/smartWallet/test-oracle-integration.js b/packages/inter-protocol/test/smartWallet/test-oracle-integration.js index d7a13525b86..81ec5ffeee5 100644 --- a/packages/inter-protocol/test/smartWallet/test-oracle-integration.js +++ b/packages/inter-protocol/test/smartWallet/test-oracle-integration.js @@ -3,17 +3,18 @@ import '@agoric/vats/src/core/types.js'; import { test as anyTest } from '@agoric/zoe/tools/prepare-test-env-ava.js'; import { NonNullish } from '@agoric/assert'; -import { coalesceUpdates } from '@agoric/smart-wallet/src/utils.js'; +import { AssetKind, makeIssuerKit } from '@agoric/ertp'; +import { zip } from '@agoric/internal'; import { eventLoopIteration } from '@agoric/internal/src/testing-utils.js'; -import buildManualTimer from '@agoric/zoe/tools/manualTimer.js'; +import { coalesceUpdates } from '@agoric/smart-wallet/src/utils.js'; import { TimeMath } from '@agoric/time'; +import buildManualTimer from '@agoric/zoe/tools/manualTimer.js'; import { E } from '@endo/far'; -import { zip } from '@agoric/internal'; -import { AssetKind, makeIssuerKit } from '@agoric/ertp'; -import { buildRootObject } from './boot-psm.js'; import { INVITATION_MAKERS_DESC as EC_INVITATION_MAKERS_DESC } from '../../src/econCommitteeCharter.js'; import { INVITATION_MAKERS_DESC as ORACLE_INVITATION_MAKERS_DESC } from '../../src/price/fluxAggregatorKit.js'; +import { instanceNameFor } from '../../src/proposals/price-feed-proposal.js'; import { headValue } from '../supports.js'; +import { buildRootObject } from './boot-psm.js'; import { currentPurseBalance, importBootTestUtils, @@ -22,9 +23,10 @@ import { } from './contexts.js'; /** - * @type {import('ava').TestFn> - * & {consume: import('@agoric/inter-protocol/src/proposals/econ-behaviors.js').EconomyBootstrapPowers['consume']}> - * } + * @typedef {Awaited> + & { consume: import('@agoric/inter-protocol/src/proposals/econ-behaviors.js').EconomyBootstrapPowers['consume'] } } TestContext */ +/** + * @type {import('ava').TestFn} */ const test = anyTest; @@ -104,7 +106,7 @@ test.before(async t => { }); /** - * @param {import('ava').ExecutionContext<*>} t + * @param {import('ava').ExecutionContext} t * @param {string[]} oracleAddresses */ const setupFeedWithWallets = async (t, oracleAddresses) => { @@ -121,7 +123,7 @@ const setupFeedWithWallets = async (t, oracleAddresses) => { /** @type {import('@agoric/zoe/src/zoeService/utils.js').Instance} */ const governedPriceAggregator = await E(agoricNames).lookup( 'instance', - 'ATOM-USD price feed', + instanceNameFor('ATOM', 'USD'), ); return { oracleWallets, governedPriceAggregator }; @@ -463,7 +465,10 @@ test.serial('govern oracles list', async t => { t.is(usedInvitations.get('acceptVoterOID')?.value[0].description, 'Voter0'); } - const feed = await E(agoricNames).lookup('instance', 'ATOM-USD price feed'); + const feed = await E(agoricNames).lookup( + 'instance', + instanceNameFor('ATOM', 'USD'), + ); t.assert(feed); // Call for a vote to addOracles //////////////////////////////// diff --git a/packages/vats/package.json b/packages/vats/package.json index faf04ae60b5..1378bb59054 100644 --- a/packages/vats/package.json +++ b/packages/vats/package.json @@ -14,6 +14,7 @@ "build:boot-viz-sim": "node src/authorityViz.js --sim-chain >docs/boot-sim.dot && dot -Tsvg docs/boot-sim.dot >docs/boot-sim.dot.svg", "build:boot-viz-sim-gov": "node src/authorityViz.js --sim-chain --gov >docs/boot-sim-gov.dot && dot -Tsvg docs/boot-sim-gov.dot >docs/boot-sim-gov.dot.svg", "build:restart-vats-proposal": "agoric run scripts/restart-vats.js", + "build:add-STARS-proposal": "agoric run scripts/add-STARS.js", "prepack": "tsc --build jsconfig.build.json", "postpack": "git clean -f '*.d.ts*'", "test": "ava", diff --git a/packages/vats/test/bootstrapTests/drivers.js b/packages/vats/test/bootstrapTests/drivers.js index aa2824d8519..4bfa1dbb684 100644 --- a/packages/vats/test/bootstrapTests/drivers.js +++ b/packages/vats/test/bootstrapTests/drivers.js @@ -4,6 +4,7 @@ import { Offers } from '@agoric/inter-protocol/src/clientSupport.js'; import { SECONDS_PER_MINUTE } from '@agoric/inter-protocol/src/proposals/econ-behaviors.js'; import { unmarshalFromVstorage } from '@agoric/internal/src/lib-chainStorage.js'; import { slotToRemotable } from '@agoric/internal/src/storage-test-utils.js'; +import { instanceNameFor } from '@agoric/inter-protocol/src/proposals/price-feed-proposal.js'; import { boardSlottingMarshaller } from '../../tools/board-utils.js'; /** @@ -144,28 +145,28 @@ export const makeWalletFactoryDriver = async ( }; /** - * @param {import('@agoric/internal/src/storage-test-utils.js').FakeStorageKit} storage + * @param {string} collateralBrandKey * @param {import('../../tools/board-utils.js').AgoricNamesRemotes} agoricNamesRemotes * @param {Awaited>} walletFactoryDriver * @param {string[]} oracleAddresses */ export const makePriceFeedDriver = async ( - storage, + collateralBrandKey, agoricNamesRemotes, walletFactoryDriver, oracleAddresses, ) => { - // XXX assumes this one feed - const priceFeedName = 'ATOM-USD price feed'; + const priceFeedName = instanceNameFor(collateralBrandKey, 'USD'); const oracleWallets = await Promise.all( oracleAddresses.map(addr => walletFactoryDriver.provideSmartWallet(addr)), ); const priceFeedInstance = agoricNamesRemotes.instance[priceFeedName]; - const adminOfferId = 'acceptOracleInvitation'; + priceFeedInstance || Fail`no price feed ${priceFeedName}`; + const adminOfferId = `accept-${collateralBrandKey}-oracleInvitation`; - // accept invitations (TODO move into driver) + // accept invitations await Promise.all( oracleWallets.map(w => w.executeOffer({ diff --git a/packages/vats/test/bootstrapTests/liquidation.js b/packages/vats/test/bootstrapTests/liquidation.js index 8d4b0724021..972212e1928 100644 --- a/packages/vats/test/bootstrapTests/liquidation.js +++ b/packages/vats/test/bootstrapTests/liquidation.js @@ -39,9 +39,15 @@ export const makeLiquidationTestContext = async t => { console.timeLog('DefaultTestContext', 'vaultFactoryKit'); // has to be late enough for agoricNames data to have been published - const agoricNamesRemotes = makeAgoricNamesRemotesFromFakeStorage( - swingsetTestKit.storage, - ); + /** @type {import('../../tools/board-utils.js').AgoricNamesRemotes} */ + const agoricNamesRemotes = {}; + const refreshAgoricNamesRemotes = () => { + Object.assign( + agoricNamesRemotes, + makeAgoricNamesRemotesFromFakeStorage(swingsetTestKit.storage), + ); + }; + refreshAgoricNamesRemotes(); agoricNamesRemotes.brand.ATOM || Fail`ATOM missing from agoricNames`; console.timeLog('DefaultTestContext', 'agoricNamesRemotes'); @@ -65,30 +71,43 @@ export const makeLiquidationTestContext = async t => { ); console.timeLog('DefaultTestContext', 'governanceDriver'); - const priceFeedDriver = await makePriceFeedDriver( - storage, - agoricNamesRemotes, - walletFactoryDriver, - // TODO read from the config file - [ - 'agoric1krunjcqfrf7la48zrvdfeeqtls5r00ep68mzkr', - 'agoric19uscwxdac6cf6z7d5e26e0jm0lgwstc47cpll8', - 'agoric144rrhh4m09mh7aaffhm6xy223ym76gve2x7y78', - 'agoric19d6gnr9fyp6hev4tlrg87zjrzsd5gzr5qlfq2p', - 'agoric1n4fcxsnkxe4gj6e24naec99hzmc4pjfdccy5nj', - ], - ); + /** @type {Record>>} */ + const priceFeedDrivers = {}; console.timeLog('DefaultTestContext', 'priceFeedDriver'); console.timeEnd('DefaultTestContext'); - const setupStartingState = async () => { + /** + * + * @param {object} opts + * @param {string} opts.collateralBrandKey + * @param {number} opts.managerIndex + */ + const setupStartingState = async ({ collateralBrandKey, managerIndex }) => { + const managerPath = `published.vaultFactory.managers.manager${managerIndex}`; const { advanceTimeBy, readLatest } = swingsetTestKit; + + !priceFeedDrivers[collateralBrandKey] || + Fail`setup for ${collateralBrandKey} already ran`; + priceFeedDrivers[collateralBrandKey] = await makePriceFeedDriver( + collateralBrandKey, + agoricNamesRemotes, + walletFactoryDriver, + // TODO read from the config file + [ + 'agoric1krunjcqfrf7la48zrvdfeeqtls5r00ep68mzkr', + 'agoric19uscwxdac6cf6z7d5e26e0jm0lgwstc47cpll8', + 'agoric144rrhh4m09mh7aaffhm6xy223ym76gve2x7y78', + 'agoric19d6gnr9fyp6hev4tlrg87zjrzsd5gzr5qlfq2p', + 'agoric1n4fcxsnkxe4gj6e24naec99hzmc4pjfdccy5nj', + ], + ); + // price feed logic treats zero time as "unset" so advance to nonzero await advanceTimeBy(1, 'seconds'); - await priceFeedDriver.setPrice(12.34); + await priceFeedDrivers[collateralBrandKey].setPrice(12.34); // raise the VaultFactory DebtLimit await governanceDriver.changeParams( @@ -102,7 +121,7 @@ export const makeLiquidationTestContext = async t => { { paramPath: { key: { - collateralBrand: agoricNamesRemotes.brand.ATOM, + collateralBrand: agoricNamesRemotes.brand[collateralBrandKey], }, }, }, @@ -120,7 +139,7 @@ export const makeLiquidationTestContext = async t => { ); // confirm Relevant Governance Parameter Assumptions - t.like(readLatest('published.vaultFactory.managers.manager0.governance'), { + t.like(readLatest(`${managerPath}.governance`), { current: { DebtLimit: { value: { value: DebtLimitValue } }, InterestRate: { @@ -168,11 +187,16 @@ export const makeLiquidationTestContext = async t => { }; const check = { - vaultNotification(vaultIndex, partial) { + /** + * @param {number} managerIndex + * @param {number} vaultIndex + * @param {Record} partial + */ + vaultNotification(managerIndex, vaultIndex, partial) { const { readLatest } = swingsetTestKit; const notification = readLatest( - `published.vaultFactory.managers.manager0.vaults.vault${vaultIndex}`, + `published.vaultFactory.managers.manager${managerIndex}.vaults.vault${vaultIndex}`, ); t.like(notification, partial); }, @@ -183,7 +207,8 @@ export const makeLiquidationTestContext = async t => { agoricNamesRemotes, check, governanceDriver, - priceFeedDriver, + priceFeedDrivers, + refreshAgoricNamesRemotes, setupStartingState, walletFactoryDriver, }; diff --git a/packages/vats/test/bootstrapTests/supports.js b/packages/vats/test/bootstrapTests/supports.js index 2ad85a28a13..9e41b7652b1 100644 --- a/packages/vats/test/bootstrapTests/supports.js +++ b/packages/vats/test/bootstrapTests/supports.js @@ -1,8 +1,10 @@ // @ts-check -import { promises as fs } from 'fs'; +/* global process */ +import * as fsAmbient from 'fs'; import { resolve as importMetaResolve } from 'import-meta-resolve'; import { basename } from 'path'; import { inspect } from 'util'; +import childProcessAmbient from 'child_process'; import { Fail } from '@agoric/assert'; import { buildSwingset } from '@agoric/cosmic-swingset/src/launch-chain.js'; @@ -191,7 +193,7 @@ export const getNodeTestVaultsConfig = async ( config.defaultManagerType = 'local'; // speed up build (60s down to 10s in testing) config.bundleCachePath = bundleDir; - await fs.mkdir(bundleDir, { recursive: true }); + await fsAmbient.promises.mkdir(bundleDir, { recursive: true }); if (config.coreProposals) { // remove Pegasus because it relies on IBC to Golang that isn't running @@ -201,10 +203,107 @@ export const getNodeTestVaultsConfig = async ( } const testConfigPath = `${bundleDir}/${basename(specifier)}`; - await fs.writeFile(testConfigPath, JSON.stringify(config), 'utf-8'); + await fsAmbient.promises.writeFile( + testConfigPath, + JSON.stringify(config), + 'utf-8', + ); return testConfigPath; }; +/** + * @param {object} powers + * @param {Pick} powers.childProcess + * @param {typeof import('node:fs/promises')} powers.fs + */ +const makeProposalExtractor = ({ childProcess, fs }) => { + const getPkgPath = (pkg, fileName = '') => + new URL(`../../../${pkg}/${fileName}`, import.meta.url).pathname; + + const runPackageScript = async (pkg, name, env) => { + console.warn(pkg, 'running package script:', name); + const pkgPath = getPkgPath(pkg); + return childProcess.execFileSync('yarn', ['run', name], { + cwd: pkgPath, + env, + }); + }; + + const loadJSON = async filePath => + harden(JSON.parse(await fs.readFile(filePath, 'utf8'))); + + // XXX parses the output to find the files but could write them to a path that can be traversed + /** @param {string} txt */ + const parseProposalParts = txt => { + const evals = [ + ...txt.matchAll(/swingset-core-eval (?\S+) (?