diff --git a/a3p-integration/proposals/a:upgrade-next/upgradeVaults.js b/a3p-integration/proposals/a:upgrade-next/upgradeVaults.js index 981daeba326..96cbd98bb1a 100644 --- a/a3p-integration/proposals/a:upgrade-next/upgradeVaults.js +++ b/a3p-integration/proposals/a:upgrade-next/upgradeVaults.js @@ -7,24 +7,35 @@ import { pushPrices, registerOraclesForBrand, } from './agd-tools.js'; +import { getDetailsMatchingVats } from './vatDetails.js'; -const BRANDNAMES = ['ATOM', 'stATOM', 'stTIA', 'stOSMO', 'stkATOM']; +const BRANDNAMES = ['ATOM', 'stATOM']; const oraclesByBrand = generateOracleMap('u16', BRANDNAMES); -// There are no old prices for the other currencies. const atomOutPre = await getPriceQuote('ATOM'); assert.equal(atomOutPre, '+12010000'); -console.log('adding oracle for each brand'); +console.log('UPGV: adding oracle for each brand'); await registerOraclesForBrand('ATOM', oraclesByBrand); await registerOraclesForBrand('stATOM', oraclesByBrand); -await registerOraclesForBrand('stTIA', oraclesByBrand); -await registerOraclesForBrand('stOSMO', oraclesByBrand); -await registerOraclesForBrand('stkATOM', oraclesByBrand); -console.log('pushing new prices'); +console.log('UPGV: pushing new prices'); await pushPrices(11.2, 'ATOM', oraclesByBrand); -await pushPrices(11.3, 'stTIA', oraclesByBrand); await pushPrices(11.4, 'stATOM', oraclesByBrand); -await pushPrices(11.5, 'stOSMO', oraclesByBrand); -await pushPrices(11.6, 'stkATOM', oraclesByBrand); + +// price_feed and governor, old and new for two tokens +const priceFeedDetails = await getDetailsMatchingVats('price_feed'); +assert.equal(Object.keys(priceFeedDetails).length, 8); + +// Two old SPAs, and two new ones +const details = await getDetailsMatchingVats('scaledPriceAuthority'); +assert.equal(Object.keys(details).length, 4, Object.keys(details)); +console.log('UPGV 8 price feeds and 4 scaledPriceAuthorities found'); + +// We previously created price feeds for some tokens that aren't in A3P +const osmoDetails = await getDetailsMatchingVats('stOSMO'); +assert.equal(Object.keys(osmoDetails).length, 0); +const tiaDetails = await getDetailsMatchingVats('stTIA'); +assert.equal(Object.keys(tiaDetails).length, 0); +const stkAtomDetails = await getDetailsMatchingVats('stkATOM'); +assert.equal(Object.keys(stkAtomDetails).length, 0); diff --git a/a3p-integration/proposals/a:upgrade-next/upgradeVaults.test.js b/a3p-integration/proposals/a:upgrade-next/upgradeVaults.test.js index 1670cdd2e92..fba273f14f1 100644 --- a/a3p-integration/proposals/a:upgrade-next/upgradeVaults.test.js +++ b/a3p-integration/proposals/a:upgrade-next/upgradeVaults.test.js @@ -23,39 +23,21 @@ import { import { getDetailsMatchingVats } from './vatDetails.js'; const checkPriceFeedVatsUpdated = async t => { - const atomDetails = await getVatDetails('ATOM-USD_price_feed'); + const atomDetails = await getVatDetails('-ATOM-USD_price_feed'); // both the original and the new ATOM vault are incarnation 0 t.is(atomDetails.incarnation, 0); const stAtomDetails = await getVatDetails('stATOM'); t.is(stAtomDetails.incarnation, 0); - const stOsmoDetails = await getVatDetails('stOSMO'); - t.is(stOsmoDetails.incarnation, 0); - const stTiaDetails = await getVatDetails('stTIA'); - t.is(stTiaDetails.incarnation, 0); - await Promise.all([ - checkForOracle(t, 'ATOM'), - checkForOracle(t, 'stATOM'), - checkForOracle(t, 'stTIA'), - checkForOracle(t, 'stOSMO'), - checkForOracle(t, 'stkATOM'), - ]); + await Promise.all([checkForOracle(t, 'ATOM'), checkForOracle(t, 'stATOM')]); }; -const BRANDNAMES = ['ATOM', 'stATOM', 'stTIA', 'stOSMO', 'stkATOM']; +const BRANDNAMES = ['ATOM', 'stATOM']; const oraclesByBrand = generateOracleMap('u16', BRANDNAMES); const checkNewQuotes = async t => { t.log('awaiting new quotes'); const atomOut = await getPriceQuote('ATOM'); t.is(atomOut, '+11200000'); - const tiaOut = await getPriceQuote('stTIA'); - t.is(tiaOut, '+11300000'); - const stAtomOut = await getPriceQuote('stATOM'); - t.is(stAtomOut, '+11400000'); - const osmoOut = await getPriceQuote('stOSMO'); - t.is(osmoOut, '+11500000'); - const stkAtomOut = await getPriceQuote('stkATOM'); - t.is(stkAtomOut, '+11600000'); }; const createNewBid = async t => { diff --git a/a3p-integration/proposals/a:upgrade-next/vatDetails.js b/a3p-integration/proposals/a:upgrade-next/vatDetails.js index ccf24608309..2cd4c2c949d 100644 --- a/a3p-integration/proposals/a:upgrade-next/vatDetails.js +++ b/a3p-integration/proposals/a:upgrade-next/vatDetails.js @@ -90,10 +90,11 @@ export const getDetailsMatchingVats = async vatName => { const infos = []; for (const vatID of vatIDs) { const vatInfo = kStore.lookupVat(vatID); + const name = vatInfo.options().name; const source = vatInfo.source(); // @ts-expect-error cast const { incarnation } = vatInfo.currentSpan(); - infos.push({ vatName, vatID, incarnation, ...source }); + infos.push({ vatName: name, vatID, incarnation, ...source }); } return infos; diff --git a/golang/cosmos/app/upgrade.go b/golang/cosmos/app/upgrade.go index 519083ddf4c..6ff4ff90916 100644 --- a/golang/cosmos/app/upgrade.go +++ b/golang/cosmos/app/upgrade.go @@ -93,7 +93,12 @@ func upgradePriceFeedCoreProposalSteps(upgradeName string) ([]vm.CoreProposalSte var inBrandNames []string switch { - case isThisUpgrade("UNRELEASED_A3P_INTEGRATION"), isThisUpgrade("UNRELEASED_main"): + case isThisUpgrade("UNRELEASED_A3P_INTEGRATION"): + inBrandNames = []string{ + "ATOM", + "stATOM", + } + case isThisUpgrade("UNRELEASED_main"): inBrandNames = []string{ "ATOM", "stATOM", @@ -148,6 +153,8 @@ func upgradePriceFeedCoreProposalSteps(upgradeName string) ([]vm.CoreProposalSte return []vm.CoreProposalStep{ // Add new vats for price feeds. The existing ones will be retired shortly. vm.CoreProposalStepForModules(proposals...), + // add new scaledPriceAuthorities. The existing ones will be retired shortly. + vm.CoreProposalStepForModules("@agoric/builders/scripts/vats/replaceScaledPriceAuthorities.js"), // Add new auction contract. The old one will be retired shortly. vm.CoreProposalStepForModules("@agoric/builders/scripts/vats/add-auction.js"), // upgrade vaultFactory. diff --git a/packages/builders/scripts/vats/upgradeScaledPriceAuthorities.js b/packages/builders/scripts/vats/replaceScaledPriceAuthorities.js similarity index 75% rename from packages/builders/scripts/vats/upgradeScaledPriceAuthorities.js rename to packages/builders/scripts/vats/replaceScaledPriceAuthorities.js index 0191830cfcd..805a6feae82 100644 --- a/packages/builders/scripts/vats/upgradeScaledPriceAuthorities.js +++ b/packages/builders/scripts/vats/replaceScaledPriceAuthorities.js @@ -4,9 +4,9 @@ import { makeHelpers } from '@agoric/deploy-script-support'; export const defaultProposalBuilder = async ({ publishRef, install }) => harden({ sourceSpec: - '@agoric/inter-protocol/src/proposals/upgrade-scaledPriceAuthorities.js', + '@agoric/inter-protocol/src/proposals/replace-scaledPriceAuthorities.js', getManifestCall: [ - 'getManifestForUpgradeScaledPriceAuthorities', + 'getManifestForReplaceScaledPriceAuthorities', { scaledPARef: publishRef( install('@agoric/zoe/src/contracts/scaledPriceAuthority.js'), @@ -18,5 +18,5 @@ export const defaultProposalBuilder = async ({ publishRef, install }) => export default async (homeP, endowments) => { const { writeCoreEval } = await makeHelpers(homeP, endowments); - await writeCoreEval('upgradeScaledPriceAuthorities', defaultProposalBuilder); + await writeCoreEval('replaceScaledPriceAuthorities', defaultProposalBuilder); }; diff --git a/packages/inter-protocol/src/proposals/addAssetToVault.js b/packages/inter-protocol/src/proposals/addAssetToVault.js index f1481e09099..671aa8fec31 100644 --- a/packages/inter-protocol/src/proposals/addAssetToVault.js +++ b/packages/inter-protocol/src/proposals/addAssetToVault.js @@ -129,7 +129,7 @@ export const publishInterchainAssetFromBank = async ( * @param {object} config.options * @param {InterchainAssetOptions} config.options.interchainAssetOptions */ -export const registerScaledPriceAuthority = async ( +export const startScaledPriceAuthority = async ( { consume: { agoricNamesAdmin, @@ -137,7 +137,6 @@ export const registerScaledPriceAuthority = async ( priceAuthorityAdmin, priceAuthority, }, - instance: { produce: produceInstance }, }, { options: { interchainAssetOptions } }, ) => { @@ -230,6 +229,25 @@ export const registerScaledPriceAuthority = async ( true, // force ); + return spaKit; +}; + +/** + * @param {BootstrapPowers} powers + * @param {object} config + * @param {object} config.options + */ +export const registerScaledPriceAuthority = async (powers, { options }) => { + const { + instance: { produce: produceInstance }, + } = powers; + + const { keyword, issuerName = keyword } = options.interchainAssetOptions; + + const spaKit = await startScaledPriceAuthority(powers, { options }); + + const label = scaledPriceFeedName(issuerName); + // publish into agoricNames so that others can await its presence. // This must stay after registerPriceAuthority above so it's evidence of registration. // eslint-disable-next-line no-restricted-syntax -- computed property diff --git a/packages/inter-protocol/src/proposals/price-feed-proposal.js b/packages/inter-protocol/src/proposals/price-feed-proposal.js index ef0808cea6e..ffe124dbd4d 100644 --- a/packages/inter-protocol/src/proposals/price-feed-proposal.js +++ b/packages/inter-protocol/src/proposals/price-feed-proposal.js @@ -227,7 +227,8 @@ export const createPriceFeed = async ( // 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. + // agoricNames. reset() in case we're replacing an existing feed. + produceInstance[AGORIC_INSTANCE_NAME].reset(); produceInstance[AGORIC_INSTANCE_NAME].resolve(faKit.instance); E(E.get(econCharterKit).creatorFacet).addInstance( diff --git a/packages/inter-protocol/src/proposals/replace-scaledPriceAuthorities.js b/packages/inter-protocol/src/proposals/replace-scaledPriceAuthorities.js new file mode 100644 index 00000000000..6ceceb74ff8 --- /dev/null +++ b/packages/inter-protocol/src/proposals/replace-scaledPriceAuthorities.js @@ -0,0 +1,119 @@ +import { makeTracer } from '@agoric/internal'; +import { E } from '@endo/far'; +import { deeplyFulfilled } from '@endo/marshal'; + +import { startScaledPriceAuthority } from './addAssetToVault.js'; +import { scaledPriceFeedName } from './utils.js'; + +const trace = makeTracer('replaceScaledPA', true); + +/** + * @param {BootstrapPowers} powers + * @param {object} config + * @param {object} config.options + */ +export const replaceScaledPriceAuthority = async (powers, { options }) => { + const { + instance: { produce: produceInstance }, + } = powers; + const { keyword, issuerName = keyword } = options.interchainAssetOptions; + + const spaKit = await startScaledPriceAuthority(powers, { options }); + + const label = scaledPriceFeedName(issuerName); + produceInstance[label].reset(); + + // publish into agoricNames so that others can await its presence. + // This must stay after registerPriceAuthority above so it's evidence of registration. + produceInstance[label].resolve(spaKit.instance); +}; + +/** + * Look up the existing assets known to auctions, and replace the corresponding + * scaledPriceAuthorities. The existing contracts will be left behind to be + * cleaned up later. + * + * @param {ChainBootstrapSpace & BootstrapPowers} powers + * @param {{ options: { scaledPARef: { bundleID: string } } }} options + */ +export const replaceScaledPriceAuthorities = async (powers, { options }) => { + trace('start'); + const { + consume: { agoricNamesAdmin, contractKits: contractKitsP, zoe }, + } = powers; + + const { scaledPARef } = options; + + const installationsAdmin = E(agoricNamesAdmin).lookupAdmin('installation'); + const [spaInstallation, contractKits] = await Promise.all([ + E(E(installationsAdmin).readonly()).lookup('scaledPriceAuthority'), + contractKitsP, + ]); + + const bundleID = scaledPARef.bundleID; + if (scaledPARef && bundleID) { + await E.when( + E(zoe).installBundleID(bundleID), + installation => + E(installationsAdmin).update('scaledPriceAuthority', installation), + err => + console.error( + `🚨 failed to update scaledPriceAuthority installation`, + err, + ), + ); + trace('installed scaledPriceAuthority bundle', bundleID); + } + + // Ask Zoe for the installation for each kit's instance, and return all the + // kits where that matches the given installation. + async function selectKitsWithInstallation(kits) { + /** @type {StartedInstanceKit[]} */ + const scaledPAKitMapP = Array.from(kits.values()).map(kit => [ + kit, + E(zoe).getInstallationForInstance(kit.instance), + ]); + const scaledPAKitMap = await deeplyFulfilled(harden(scaledPAKitMapP)); + const scaledPAKitEntries = []; + for (const [instance, installation] of scaledPAKitMap) { + if (spaInstallation === installation) { + scaledPAKitEntries.push(instance); + } + } + return scaledPAKitEntries; + } + const scaledPAKitEntries = await selectKitsWithInstallation(contractKits); + + for (const kitEntry of scaledPAKitEntries) { + trace({ kitEntry }); + + const keyword = kitEntry.label.match(/scaledPriceAuthority-(.*)/)[1]; + const interchainAssetOptions = { keyword }; + await replaceScaledPriceAuthority(powers, { + options: { interchainAssetOptions }, + }); + } +}; + +const t = 'replaceScaledPriceAuthority'; +export const getManifestForReplaceScaledPriceAuthorities = async ( + _ign, + upgradeSPAOptions, +) => ({ + manifest: { + [replaceScaledPriceAuthorities.name]: { + consume: { + agoricNamesAdmin: t, + contractKits: t, + priceAuthority: t, + priceAuthorityAdmin: t, + zoe: t, + startUpgradable: t, + }, + instance: { + produce: t, + }, + }, + }, + options: { ...upgradeSPAOptions }, +}); diff --git a/packages/inter-protocol/src/proposals/upgrade-scaledPriceAuthorities.js b/packages/inter-protocol/src/proposals/upgrade-scaledPriceAuthorities.js deleted file mode 100644 index d208987830f..00000000000 --- a/packages/inter-protocol/src/proposals/upgrade-scaledPriceAuthorities.js +++ /dev/null @@ -1,78 +0,0 @@ -import { makeTracer } from '@agoric/internal'; -import { E } from '@endo/far'; - -const trace = makeTracer('upgradeScaledPA', true); - -/** - * @param {ChainBootstrapSpace} powers - * @param {{ options: { scaledPARef: { bundleID: string } } }} options - */ -export const upgradeScaledPriceAuthorities = async ( - { - consume: { - agoricNamesAdmin, - contractKits: contractKitsP, - instancePrivateArgs: instancePrivateArgsP, - zoe, - }, - }, - { options }, -) => { - trace('start'); - const { scaledPARef } = options; - await null; - - const bundleID = scaledPARef.bundleID; - if (scaledPARef && bundleID) { - await E.when( - E(zoe).installBundleID(bundleID), - installation => - E(E(agoricNamesAdmin).lookupAdmin('installation')).update( - 'scaledPriceAuthority', - installation, - ), - err => - console.error( - `🚨 failed to update scaledPriceAuthority installation`, - err, - ), - ); - } - - const [contractKits, instancePrivateArgs] = await Promise.all([ - contractKitsP, - instancePrivateArgsP, - ]); - /** @type {StartedInstanceKit[]} */ - const scaledPAKitEntries = Array.from(contractKits.values()).filter( - kit => kit.label && kit.label.match(/scaledPriceAuthority/), - ); - - for (const kitEntry of scaledPAKitEntries) { - const { instance } = kitEntry; - const privateArgs = instancePrivateArgs.get(instance); - trace('upgrade scaledPriceAuthority', kitEntry.label); - await E(kitEntry.adminFacet).upgradeContract(bundleID, privateArgs); - } -}; - -const t = 'upgradeScaledPriceAuthority'; -export const getManifestForUpgradeScaledPriceAuthorities = async ( - _ign, - upgradeSPAOptions, -) => ({ - manifest: { - [upgradeScaledPriceAuthorities.name]: { - consume: { - agoricNamesAdmin: t, - contractKits: t, - instancePrivateArgs: t, - zoe: t, - }, - instance: { - produce: t, - }, - }, - }, - options: { ...upgradeSPAOptions }, -}); diff --git a/packages/zoe/src/contractSupport/priceAuthority.js b/packages/zoe/src/contractSupport/priceAuthority.js index ae88e854ab0..17568719e2a 100644 --- a/packages/zoe/src/contractSupport/priceAuthority.js +++ b/packages/zoe/src/contractSupport/priceAuthority.js @@ -3,7 +3,6 @@ import { q, Fail } from '@endo/errors'; import { E } from '@endo/eventual-send'; import { Far } from '@endo/marshal'; import { makePromiseKit } from '@endo/promise-kit'; - import { AmountMath, AmountShape, BrandShape } from '@agoric/ertp'; import { makeNotifier } from '@agoric/notifier'; import { makeTracer } from '@agoric/internal'; @@ -259,7 +258,7 @@ export const makeOnewayPriceAuthorityKit = opts => { }; /** @type {PriceAuthority} */ - const priceAuthority = Far('PriceAuthority', { + const priceAuthority = Far('PriceAggregator', { getQuoteIssuer(brandIn, brandOut) { assertBrands(brandIn, brandOut); return quoteIssuer; diff --git a/packages/zoe/src/contractSupport/priceAuthorityInitial.js b/packages/zoe/src/contractSupport/priceAuthorityInitial.js index 110c5affc9b..f3fad5f8d8c 100644 --- a/packages/zoe/src/contractSupport/priceAuthorityInitial.js +++ b/packages/zoe/src/contractSupport/priceAuthorityInitial.js @@ -110,7 +110,7 @@ export const makeInitialTransform = ( : quoteP; }; - return Far('PriceAuthority', { + return Far('InitializedScaledPriceAuthority', { ...priceAuthority, makeQuoteNotifier, quoteGiven, diff --git a/packages/zoe/src/contractSupport/priceAuthorityTransform.js b/packages/zoe/src/contractSupport/priceAuthorityTransform.js index ce2d79217b9..ac6425c2b9f 100644 --- a/packages/zoe/src/contractSupport/priceAuthorityTransform.js +++ b/packages/zoe/src/contractSupport/priceAuthorityTransform.js @@ -182,7 +182,7 @@ export const makePriceAuthorityTransform = async ({ }; /** @type {PriceAuthority} */ - const priceAuthority = Far('PriceAuthority', { + const priceAuthority = Far('ScaledPriceAuthority', { getQuoteIssuer(brandIn, brandOut) { assertBrands(brandIn, brandOut); return quoteIssuer; diff --git a/packages/zoe/src/contracts/scaledPriceAuthority.js b/packages/zoe/src/contracts/scaledPriceAuthority.js index b558a315c16..1b1acd212e5 100644 --- a/packages/zoe/src/contracts/scaledPriceAuthority.js +++ b/packages/zoe/src/contracts/scaledPriceAuthority.js @@ -26,7 +26,7 @@ import { provideQuoteMint } from '../contractSupport/priceAuthorityQuoteMint.js' * via ratios. * * No durable state. Because it only transforms there's nothing important to save. - * However that also means that the contract terms cannot be modified and should + * However, that also means that the contract terms cannot be modified and should * a `sourcePriceAuthority` reference sever this contract will break. A future version * could allow changing that term through privateArgs or governance. * @@ -52,7 +52,9 @@ export const prepare = async (zcf, privateArgs, baggage) => { const priceAuthority = makePriceAuthorityTransform({ quoteMint, - sourcePriceAuthority, + // If the priceAuthority is overridden in privateArgs, use that version + sourcePriceAuthority: + privateArgs?.newPriceAuthority || sourcePriceAuthority, sourceBrandIn, sourceBrandOut, actualBrandIn,