From 84fb4f5f1b85bdac554990b647c997b23d8ea230 Mon Sep 17 00:00:00 2001 From: Chris Hibbert Date: Thu, 22 Aug 2024 16:52:00 -0700 Subject: [PATCH 01/21] refactor: rename e:upgrade-next to n:upgrade-next --- .../e:upgrade-next/priceFeed-follower-auction.test.js | 8 -------- .../{e:upgrade-next => n:upgrade-next}/.gitignore | 0 .../{e:upgrade-next => n:upgrade-next}/.yarnrc.yml | 0 .../{e:upgrade-next => n:upgrade-next}/README.md | 0 .../{e:upgrade-next => n:upgrade-next}/agd-tools.js | 0 .../{e:upgrade-next => n:upgrade-next}/initial.test.js | 0 .../{e:upgrade-next => n:upgrade-next}/localchain.test.js | 0 .../{e:upgrade-next => n:upgrade-next}/package.json | 0 .../{e:upgrade-next => n:upgrade-next}/prepare.sh | 0 .../provisionPool.test.js | 0 .../synthetic-chain-excerpt.js | 0 .../proposals/{e:upgrade-next => n:upgrade-next}/test.sh | 0 .../{e:upgrade-next => n:upgrade-next}/tsconfig.json | 0 .../{e:upgrade-next => n:upgrade-next}/vatDetails.js | 0 .../{e:upgrade-next => n:upgrade-next}/yarn.lock | 0 15 files changed, 8 deletions(-) delete mode 100644 a3p-integration/proposals/e:upgrade-next/priceFeed-follower-auction.test.js rename a3p-integration/proposals/{e:upgrade-next => n:upgrade-next}/.gitignore (100%) rename a3p-integration/proposals/{e:upgrade-next => n:upgrade-next}/.yarnrc.yml (100%) rename a3p-integration/proposals/{e:upgrade-next => n:upgrade-next}/README.md (100%) rename a3p-integration/proposals/{e:upgrade-next => n:upgrade-next}/agd-tools.js (100%) rename a3p-integration/proposals/{e:upgrade-next => n:upgrade-next}/initial.test.js (100%) rename a3p-integration/proposals/{e:upgrade-next => n:upgrade-next}/localchain.test.js (100%) rename a3p-integration/proposals/{e:upgrade-next => n:upgrade-next}/package.json (100%) rename a3p-integration/proposals/{e:upgrade-next => n:upgrade-next}/prepare.sh (100%) rename a3p-integration/proposals/{e:upgrade-next => n:upgrade-next}/provisionPool.test.js (100%) rename a3p-integration/proposals/{e:upgrade-next => n:upgrade-next}/synthetic-chain-excerpt.js (100%) rename a3p-integration/proposals/{e:upgrade-next => n:upgrade-next}/test.sh (100%) rename a3p-integration/proposals/{e:upgrade-next => n:upgrade-next}/tsconfig.json (100%) rename a3p-integration/proposals/{e:upgrade-next => n:upgrade-next}/vatDetails.js (100%) rename a3p-integration/proposals/{e:upgrade-next => n:upgrade-next}/yarn.lock (100%) diff --git a/a3p-integration/proposals/e:upgrade-next/priceFeed-follower-auction.test.js b/a3p-integration/proposals/e:upgrade-next/priceFeed-follower-auction.test.js deleted file mode 100644 index 30ae903b075..00000000000 --- a/a3p-integration/proposals/e:upgrade-next/priceFeed-follower-auction.test.js +++ /dev/null @@ -1,8 +0,0 @@ -import test from 'ava'; -import { getDetailsMatchingVats } from './vatDetails.js'; - -test('new auction vat', async t => { - const details = await getDetailsMatchingVats('auctioneer'); - // This query matches both the auction and its governor, so 2*2 - t.is(Object.keys(details).length, 4); -}); diff --git a/a3p-integration/proposals/e:upgrade-next/.gitignore b/a3p-integration/proposals/n:upgrade-next/.gitignore similarity index 100% rename from a3p-integration/proposals/e:upgrade-next/.gitignore rename to a3p-integration/proposals/n:upgrade-next/.gitignore diff --git a/a3p-integration/proposals/e:upgrade-next/.yarnrc.yml b/a3p-integration/proposals/n:upgrade-next/.yarnrc.yml similarity index 100% rename from a3p-integration/proposals/e:upgrade-next/.yarnrc.yml rename to a3p-integration/proposals/n:upgrade-next/.yarnrc.yml diff --git a/a3p-integration/proposals/e:upgrade-next/README.md b/a3p-integration/proposals/n:upgrade-next/README.md similarity index 100% rename from a3p-integration/proposals/e:upgrade-next/README.md rename to a3p-integration/proposals/n:upgrade-next/README.md diff --git a/a3p-integration/proposals/e:upgrade-next/agd-tools.js b/a3p-integration/proposals/n:upgrade-next/agd-tools.js similarity index 100% rename from a3p-integration/proposals/e:upgrade-next/agd-tools.js rename to a3p-integration/proposals/n:upgrade-next/agd-tools.js diff --git a/a3p-integration/proposals/e:upgrade-next/initial.test.js b/a3p-integration/proposals/n:upgrade-next/initial.test.js similarity index 100% rename from a3p-integration/proposals/e:upgrade-next/initial.test.js rename to a3p-integration/proposals/n:upgrade-next/initial.test.js diff --git a/a3p-integration/proposals/e:upgrade-next/localchain.test.js b/a3p-integration/proposals/n:upgrade-next/localchain.test.js similarity index 100% rename from a3p-integration/proposals/e:upgrade-next/localchain.test.js rename to a3p-integration/proposals/n:upgrade-next/localchain.test.js diff --git a/a3p-integration/proposals/e:upgrade-next/package.json b/a3p-integration/proposals/n:upgrade-next/package.json similarity index 100% rename from a3p-integration/proposals/e:upgrade-next/package.json rename to a3p-integration/proposals/n:upgrade-next/package.json diff --git a/a3p-integration/proposals/e:upgrade-next/prepare.sh b/a3p-integration/proposals/n:upgrade-next/prepare.sh similarity index 100% rename from a3p-integration/proposals/e:upgrade-next/prepare.sh rename to a3p-integration/proposals/n:upgrade-next/prepare.sh diff --git a/a3p-integration/proposals/e:upgrade-next/provisionPool.test.js b/a3p-integration/proposals/n:upgrade-next/provisionPool.test.js similarity index 100% rename from a3p-integration/proposals/e:upgrade-next/provisionPool.test.js rename to a3p-integration/proposals/n:upgrade-next/provisionPool.test.js diff --git a/a3p-integration/proposals/e:upgrade-next/synthetic-chain-excerpt.js b/a3p-integration/proposals/n:upgrade-next/synthetic-chain-excerpt.js similarity index 100% rename from a3p-integration/proposals/e:upgrade-next/synthetic-chain-excerpt.js rename to a3p-integration/proposals/n:upgrade-next/synthetic-chain-excerpt.js diff --git a/a3p-integration/proposals/e:upgrade-next/test.sh b/a3p-integration/proposals/n:upgrade-next/test.sh similarity index 100% rename from a3p-integration/proposals/e:upgrade-next/test.sh rename to a3p-integration/proposals/n:upgrade-next/test.sh diff --git a/a3p-integration/proposals/e:upgrade-next/tsconfig.json b/a3p-integration/proposals/n:upgrade-next/tsconfig.json similarity index 100% rename from a3p-integration/proposals/e:upgrade-next/tsconfig.json rename to a3p-integration/proposals/n:upgrade-next/tsconfig.json diff --git a/a3p-integration/proposals/e:upgrade-next/vatDetails.js b/a3p-integration/proposals/n:upgrade-next/vatDetails.js similarity index 100% rename from a3p-integration/proposals/e:upgrade-next/vatDetails.js rename to a3p-integration/proposals/n:upgrade-next/vatDetails.js diff --git a/a3p-integration/proposals/e:upgrade-next/yarn.lock b/a3p-integration/proposals/n:upgrade-next/yarn.lock similarity index 100% rename from a3p-integration/proposals/e:upgrade-next/yarn.lock rename to a3p-integration/proposals/n:upgrade-next/yarn.lock From 4eb168b266541befb95ff4da8c646c7422b3615f Mon Sep 17 00:00:00 2001 From: Chris Hibbert Date: Thu, 29 Aug 2024 14:15:41 -0700 Subject: [PATCH 02/21] chore: setup f:replace-price-feeds --- .../proposals/f:replace-price-feeds/README.md | 10 + .../f:replace-price-feeds/agd-tools.js | 208 ++++++++++++++++++ .../f:replace-price-feeds/package.json | 34 +++ .../proposals/f:replace-price-feeds/test.sh | 6 + 4 files changed, 258 insertions(+) create mode 100644 a3p-integration/proposals/f:replace-price-feeds/README.md create mode 100644 a3p-integration/proposals/f:replace-price-feeds/agd-tools.js create mode 100644 a3p-integration/proposals/f:replace-price-feeds/package.json create mode 100755 a3p-integration/proposals/f:replace-price-feeds/test.sh diff --git a/a3p-integration/proposals/f:replace-price-feeds/README.md b/a3p-integration/proposals/f:replace-price-feeds/README.md new file mode 100644 index 00000000000..414bb07fe81 --- /dev/null +++ b/a3p-integration/proposals/f:replace-price-feeds/README.md @@ -0,0 +1,10 @@ +# CoreEvalProposal to replace existing price_feed and scaledPriceAuthority vats +# with new contracts. Auctions will need to be replaced, and Vaults will need to +# get at least a null upgrade in order to make use of the new prices. Oracle +# operators will need to accept new invitations, and sync to the roundId (0) of +# the new contracts in order to feed the new pipelines. + +The `submission` for this proposal is automatically generated during `yarn build` +in [a3p-integration](../..) using the code in agoric-sdk through +[build-all-submissions.sh](../../scripts/build-all-submissions.sh) and +[build-submission.sh](../../scripts/build-submission.sh). diff --git a/a3p-integration/proposals/f:replace-price-feeds/agd-tools.js b/a3p-integration/proposals/f:replace-price-feeds/agd-tools.js new file mode 100644 index 00000000000..550668e110b --- /dev/null +++ b/a3p-integration/proposals/f:replace-price-feeds/agd-tools.js @@ -0,0 +1,208 @@ +import { + agd, + agops, + agopsLocation, + CHAINID, + executeCommand, + executeOffer, + GOV1ADDR, + GOV2ADDR, + GOV3ADDR, + newOfferId, + VALIDATORADDR, +} from '@agoric/synthetic-chain'; + +const ORACLE_ADDRESSES = [GOV1ADDR, GOV2ADDR, GOV3ADDR]; + +export const BID_OFFER_ID = 'bid-vaultUpgrade-test3'; + +const queryVstorage = path => + agd.query('vstorage', 'data', '--output', 'json', path); + +// XXX use endo/marshal? +const getQuoteBody = async path => { + const queryOut = await queryVstorage(path); + + const body = JSON.parse(JSON.parse(queryOut.value).values[0]); + return JSON.parse(body.body.substring(1)); +}; + +export const getOracleInstance = async price => { + const instanceRec = await queryVstorage(`published.agoricNames.instance`); + + const value = JSON.parse(instanceRec.value); + const body = JSON.parse(value.values.at(-1)); + + const feeds = JSON.parse(body.body.substring(1)); + const feedName = `${price}-USD price feed`; + + const key = Object.keys(feeds).find(k => feeds[k][0] === feedName); + if (key) { + return body.slots[key]; + } + return null; +}; + +export const checkForOracle = async (t, name) => { + const instance = await getOracleInstance(name); + t.truthy(instance); +}; + +export const registerOraclesForBrand = async (brandIn, oraclesByBrand) => { + await null; + const promiseArray = []; + + const oraclesWithID = oraclesByBrand.get(brandIn); + for (const oracle of oraclesWithID) { + const { address, offerId } = oracle; + promiseArray.push( + executeOffer( + address, + agops.oracle('accept', '--offerId', offerId, `--pair ${brandIn}.USD`), + ), + ); + } + + return Promise.all(promiseArray); +}; + +/** + * Generate a consistent map of oracleIDs for a brand that can be used to + * register oracles or to push prices. The baseID changes each time new + * invitations are sent/accepted, and need to be maintained as constants in + * scripts that use the oracles. Each oracleAddress and brand needs a unique + * offerId, so we create recoverable IDs using the brandName and oracle id, + * mixed with the upgrade at which the invitations were accepted. + * + * @param {string} baseId + * @param {string} brandName + */ +const addOraclesForBrand = (baseId, brandName) => { + const oraclesWithID = []; + for (let i = 0; i < ORACLE_ADDRESSES.length; i += 1) { + const oracleAddress = ORACLE_ADDRESSES[i]; + const offerId = `${brandName}.${baseId}.${i}`; + oraclesWithID.push({ address: oracleAddress, offerId }); + } + return oraclesWithID; +}; + +export const addPreexistingOracles = async (brandIn, oraclesByBrand) => { + await null; + + const oraclesWithID = []; + for (let i = 0; i < ORACLE_ADDRESSES.length; i += 1) { + const oracleAddress = ORACLE_ADDRESSES[i]; + + const path = `published.wallet.${oracleAddress}.current`; + const wallet = await getQuoteBody(path); + const idToInvitation = wallet.offerToUsedInvitation.find(([k]) => { + return !isNaN(k[0]); + }); + if (idToInvitation) { + oraclesWithID.push({ + address: oracleAddress, + offerId: idToInvitation[0], + }); + } else { + console.log('AGD addO skip', oraclesWithID); + } + } + + oraclesByBrand.set(brandIn, oraclesWithID); +}; + +/** + * Generate a consistent map of oracleIDs and brands that can be used to + * register oracles or to push prices. The baseID changes each time new + * invitations are sent/accepted, and need to be maintained as constants in + * scripts that use these records to push prices. + * + * @param {string} baseId + * @param {string[]} brandNames + */ +export const generateOracleMap = (baseId, brandNames) => { + const oraclesByBrand = new Map(); + for (const brandName of brandNames) { + const oraclesWithID = addOraclesForBrand(baseId, brandName); + oraclesByBrand.set(brandName, oraclesWithID); + } + return oraclesByBrand; +}; + +export const pushPrices = (price, brandIn, oraclesByBrand) => { + const promiseArray = []; + + for (const oracle of oraclesByBrand.get(brandIn)) { + promiseArray.push( + executeOffer( + oracle.address, + agops.oracle( + 'pushPriceRound', + '--price', + price, + '--oracleAdminAcceptOfferId', + oracle.offerId, + ), + ), + ); + } + + return Promise.all(promiseArray); +}; + +export const getPriceQuote = async price => { + const path = `published.priceFeed.${price}-USD_price_feed`; + const body = await getQuoteBody(path); + return body.amountOut.value; +}; + +export const agopsInter = (...params) => { + const newParams = ['inter', ...params]; + return executeCommand(agopsLocation, newParams); +}; + +export const createBid = (price, addr, offerId) => { + return agopsInter( + 'bid', + 'by-price', + `--price ${price}`, + `--give 1.0IST`, + '--from', + addr, + '--keyring-backend test', + `--offer-id ${offerId}`, + ); +}; + +export const getLiveOffers = async addr => { + const path = `published.wallet.${addr}.current`; + const body = await getQuoteBody(path); + return body.liveOffers; +}; + +export const getAuctionCollateral = async index => { + const path = `published.auction.book${index}`; + const body = await getQuoteBody(path); + return body.collateralAvailable.value; +}; + +export const getVaultPrices = async index => { + const path = `published.vaultFactory.managers.manager${index}.quotes`; + const body = await getQuoteBody(path); + return body.quoteAmount; +}; + +export const bankSend = (addr, wanted) => { + const chain = ['--chain-id', CHAINID]; + const from = ['--from', VALIDATORADDR]; + const testKeyring = ['--keyring-backend', 'test']; + const noise = [...from, ...chain, ...testKeyring, '--yes']; + + return agd.tx('bank', 'send', VALIDATORADDR, addr, wanted, ...noise); +}; + +export const getProvisionPoolMetrics = async () => { + const path = `published.provisionPool.metrics`; + return getQuoteBody(path); +}; diff --git a/a3p-integration/proposals/f:replace-price-feeds/package.json b/a3p-integration/proposals/f:replace-price-feeds/package.json new file mode 100644 index 00000000000..fe1c38a203a --- /dev/null +++ b/a3p-integration/proposals/f:replace-price-feeds/package.json @@ -0,0 +1,34 @@ +{ + "agoricProposal": { + "releaseNotes": false, + "sdkImageTag": "unreleased", + "planName": "UNRELEASED_A3P_INTEGRATION", + "upgradeInfo": { + "coreProposals": [] + }, + "sdk-generate": [ + "vats/replacePriceFeeds.js", + "vats/replace-scaledPriceAuthorities.js", + "vats/add-auction.js", + "vats/upgradeVaults.js" + ], + "type": "Software Upgrade Proposal" + }, + "type": "module", + "license": "Apache-2.0", + "dependencies": { + "@agoric/synthetic-chain": "^0.1.0", + "ava": "^5.3.1" + }, + "ava": { + "concurrency": 1, + "timeout": "2m", + "files": [ + "!submission" + ] + }, + "scripts": { + "agops": "yarn --cwd /usr/src/agoric-sdk/ --silent agops" + }, + "packageManager": "yarn@4.2.2" +} diff --git a/a3p-integration/proposals/f:replace-price-feeds/test.sh b/a3p-integration/proposals/f:replace-price-feeds/test.sh new file mode 100755 index 00000000000..23a194f7f79 --- /dev/null +++ b/a3p-integration/proposals/f:replace-price-feeds/test.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +# Place here any test that should be executed using the proposal. +# The effects of this step are not persisted in further layers. + +yarn ava ./*.test.js From b88c330cb45e7ed41964ae79cb2e444536a28b70 Mon Sep 17 00:00:00 2001 From: Chris Hibbert Date: Thu, 29 Aug 2024 14:21:48 -0700 Subject: [PATCH 03/21] feat: start of a proposal for replacing priceFeeds --- .../inter-protocol/updatePriceFeeds.js | 61 +++++++++++++++++++ .../src/proposals/price-feed-proposal.js | 3 +- 2 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 packages/builders/scripts/inter-protocol/updatePriceFeeds.js diff --git a/packages/builders/scripts/inter-protocol/updatePriceFeeds.js b/packages/builders/scripts/inter-protocol/updatePriceFeeds.js new file mode 100644 index 00000000000..d3b22f57dae --- /dev/null +++ b/packages/builders/scripts/inter-protocol/updatePriceFeeds.js @@ -0,0 +1,61 @@ +import { makeHelpers } from '@agoric/deploy-script-support'; +import { strictPriceFeedProposalBuilder } from '../vats/priceFeedSupport.js'; + +const configurations = { + UNRELEASED_A3P_INTEGRATION: { + oracleAddresses: [ + 'agoric1lu9hh5vgx05hmlpfu47hukershgdxctk6l5s05', // GOV1 + 'agoric15lpnq2mjsdhtztf6khp7mrsq66hyrssspy92pd', // GOV2 + 'agoric1mwm224epc4l3pjcz7qsxnudcuktpynwkmnfqfp', // GOV3 + ], + inBrandNames: ['ATOM', 'stATOM'], + }, + UNRELEASED_main: { + oracleAddresses: [ + 'agoric144rrhh4m09mh7aaffhm6xy223ym76gve2x7y78', // DSRV + 'agoric19d6gnr9fyp6hev4tlrg87zjrzsd5gzr5qlfq2p', // Stakin + 'agoric19uscwxdac6cf6z7d5e26e0jm0lgwstc47cpll8', // 01node + 'agoric1krunjcqfrf7la48zrvdfeeqtls5r00ep68mzkr', // Simply Staking + 'agoric1n4fcxsnkxe4gj6e24naec99hzmc4pjfdccy5nj', // P2P + ], + inBrandName: ['ATOM', 'stATOM', 'stOSMO', 'stTIA', 'stkATOM'], + }, + UNRELEASED_devnet: { + oracleAddresses: [ + 'agoric1lw4e4aas9q84tq0q92j85rwjjjapf8dmnllnft', // DSRV + 'agoric1zj6vrrrjq4gsyr9lw7dplv4vyejg3p8j2urm82', // Stakin + 'agoric1ra0g6crtsy6r3qnpu7ruvm7qd4wjnznyzg5nu4', // 01node + 'agoric1qj07c7vfk3knqdral0sej7fa6eavkdn8vd8etf', // Simply Staking + 'agoric10vjkvkmpp9e356xeh6qqlhrny2htyzp8hf88fk', // P2P + ], + inBrandNames: ['ATOM', 'stTIA', 'stkATOM'], + }, +}; + +export default async (homeP, endowments) => { + const upgradeEnvironment = endowments.scriptArgs?.[0]; + console.log('UPPrices', upgradeEnvironment); + + const { writeCoreEval } = await makeHelpers(homeP, endowments); + + const coreEvalSteps = []; + for (const config of configurations[upgradeEnvironment]) { + const { inBrandNames, oracleAddresses } = config; + for (const inBrandName of inBrandNames) { + const options = { + AGORIC_INSTANCE_NAME: `${inBrandName}-USD price feed`, + ORACLE_ADDRESSES: oracleAddresses, + IN_BRAND_LOOKUP: ['agoricNames', 'oracleBrand', inBrandName], + }; + coreEvalSteps.push( + writeCoreEval(options.AGORIC_INSTANCE_NAME, opts => + strictPriceFeedProposalBuilder({ ...opts, ...options }), + ), + ); + } + } + + // TODO(hibbert) leave a marker in promise space as a signal to vaults upgrade + + await Promise.all(coreEvalSteps); +}; 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( From 089bbfd347b3fad81277638d75f137c4713f408f Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Mon, 9 Sep 2024 13:20:51 -0500 Subject: [PATCH 04/21] test: PriceFeedDriver.refreshInvitations() When a new price aggregator is launched, oracle operators need to accept new invitations. --- packages/boot/tools/drivers.ts | 44 ++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/packages/boot/tools/drivers.ts b/packages/boot/tools/drivers.ts index 6f36e56aeb7..33453f5d884 100644 --- a/packages/boot/tools/drivers.ts +++ b/packages/boot/tools/drivers.ts @@ -151,24 +151,28 @@ export const makePriceFeedDriver = async ( oracleAddresses.map(addr => walletFactoryDriver.provideSmartWallet(addr)), ); - const priceFeedInstance = agoricNamesRemotes.instance[priceFeedName]; - priceFeedInstance || Fail`no price feed ${priceFeedName}`; - const adminOfferId = `accept-${collateralBrandKey}-oracleInvitation`; - - // accept invitations - await Promise.all( - oracleWallets.map(w => - w.executeOffer({ - id: adminOfferId, - invitationSpec: { - source: 'purse', - instance: priceFeedInstance, - description: 'oracle invitation', - }, - proposal: {}, - }), - ), - ); + let nonce = 0; + let adminOfferId; + const acceptInvitations = async () => { + const priceFeedInstance = agoricNamesRemotes.instance[priceFeedName]; + priceFeedInstance || Fail`no price feed ${priceFeedName}`; + nonce += 1; + adminOfferId = `accept-${collateralBrandKey}-oracleInvitation${nonce}`; + return Promise.all( + oracleWallets.map(w => + w.executeOffer({ + id: adminOfferId, + invitationSpec: { + source: 'purse', + instance: priceFeedInstance, + description: 'oracle invitation', + }, + proposal: {}, + }), + ), + ); + }; + await acceptInvitations(); // zero is the initial lastReportedRoundId so causes an error: cannot report on previous rounds let roundId = 1n; @@ -192,6 +196,10 @@ export const makePriceFeedDriver = async ( roundId += 1n; // TODO confirm the new price is written to storage }, + async refreshInvitations() { + roundId = 1n; + await acceptInvitations(); + }, }; }; harden(makePriceFeedDriver); From 45c99279929826e2cb4c8a30257f5628dc6f1d9f Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Mon, 9 Sep 2024 13:21:53 -0500 Subject: [PATCH 05/21] test: export ATOM oracleAddresses from liquidation support --- packages/boot/tools/liquidation.ts | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/packages/boot/tools/liquidation.ts b/packages/boot/tools/liquidation.ts index 221f98e7260..a2dd9dbc25f 100644 --- a/packages/boot/tools/liquidation.ts +++ b/packages/boot/tools/liquidation.ts @@ -53,6 +53,17 @@ export type LiquidationSetup = { }; }; +// TODO read from the config file +export const atomConfig = { + oracleAddresses: [ + 'agoric1krunjcqfrf7la48zrvdfeeqtls5r00ep68mzkr', + 'agoric19uscwxdac6cf6z7d5e26e0jm0lgwstc47cpll8', + 'agoric144rrhh4m09mh7aaffhm6xy223ym76gve2x7y78', + 'agoric19d6gnr9fyp6hev4tlrg87zjrzsd5gzr5qlfq2p', + 'agoric1n4fcxsnkxe4gj6e24naec99hzmc4pjfdccy5nj', + ], +}; + export const scale6 = x => BigInt(Math.round(x * 1_000_000)); const DebtLimitValue = scale6(100_000); @@ -103,14 +114,7 @@ export const makeLiquidationTestKit = async ({ collateralBrandKey, agoricNamesRemotes, walletFactoryDriver, - // TODO read from the config file - [ - 'agoric1krunjcqfrf7la48zrvdfeeqtls5r00ep68mzkr', - 'agoric19uscwxdac6cf6z7d5e26e0jm0lgwstc47cpll8', - 'agoric144rrhh4m09mh7aaffhm6xy223ym76gve2x7y78', - 'agoric19d6gnr9fyp6hev4tlrg87zjrzsd5gzr5qlfq2p', - 'agoric1n4fcxsnkxe4gj6e24naec99hzmc4pjfdccy5nj', - ], + atomConfig.oracleAddresses, ); } From 739c80679b0656b293782904cd174209a17e01ad Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Mon, 9 Sep 2024 13:22:48 -0500 Subject: [PATCH 06/21] test: thread $SLOGFILE thru liquidation setup --- packages/boot/tools/liquidation.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/boot/tools/liquidation.ts b/packages/boot/tools/liquidation.ts index a2dd9dbc25f..1945abdc9fe 100644 --- a/packages/boot/tools/liquidation.ts +++ b/packages/boot/tools/liquidation.ts @@ -308,8 +308,14 @@ export const makeLiquidationTestKit = async ({ }; }; -export const makeLiquidationTestContext = async t => { - const swingsetTestKit = await makeSwingsetTestKit(t.log); +export const makeLiquidationTestContext = async ( + t, + io: { env?: Record } = {}, +) => { + const { env = {} } = io; + const swingsetTestKit = await makeSwingsetTestKit(t.log, undefined, { + slogFile: env.SLOGFILE, + }); console.time('DefaultTestContext'); const { runUtils, storage } = swingsetTestKit; From bbc08f2ba14df1a9d863b90456c1c7e774857a1c Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Mon, 9 Sep 2024 13:23:46 -0500 Subject: [PATCH 07/21] feat: makeProposalExtractor supports cliArgs --- packages/boot/tools/supports.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/boot/tools/supports.ts b/packages/boot/tools/supports.ts index 36af57d01cc..3ebfca50da2 100644 --- a/packages/boot/tools/supports.ts +++ b/packages/boot/tools/supports.ts @@ -154,6 +154,7 @@ export const makeProposalExtractor = ({ childProcess, fs }: Powers) => { outputDir: string, scriptPath: string, env: NodeJS.ProcessEnv, + cliArgs: string[] = [], ) => { console.info('running package script:', scriptPath); const out = childProcess.execFileSync('yarn', ['bin', 'agoric'], { @@ -162,7 +163,7 @@ export const makeProposalExtractor = ({ childProcess, fs }: Powers) => { }); return childProcess.execFileSync( out.toString().trim(), - ['run', scriptPath], + ['run', scriptPath, ...cliArgs], { cwd: outputDir, env, @@ -193,16 +194,21 @@ export const makeProposalExtractor = ({ childProcess, fs }: Powers) => { return { evals, bundles }; }; - const buildAndExtract = async (builderPath: string) => { + const buildAndExtract = async ( + builderPath: string, + opts?: Record, + ) => { const tmpDir = await fsAmbientPromises.mkdtemp( join(getPkgPath('builders'), 'proposal-'), ); + const args = opts ? [JSON.stringify(opts)] : []; const built = parseProposalParts( runPackageScript( tmpDir, await importSpec(builderPath), process.env, + args, ).toString(), ); From 51c1516916a56d0470feeb5ffaab14145690a440 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Mon, 9 Sep 2024 13:58:32 -0500 Subject: [PATCH 08/21] chore: add parameterized builder to priceFeedSupport --- .../builders/scripts/vats/priceFeedSupport.js | 53 +++++++++++++++---- 1 file changed, 44 insertions(+), 9 deletions(-) diff --git a/packages/builders/scripts/vats/priceFeedSupport.js b/packages/builders/scripts/vats/priceFeedSupport.js index 5f916d3c793..8a975bfdcc0 100644 --- a/packages/builders/scripts/vats/priceFeedSupport.js +++ b/packages/builders/scripts/vats/priceFeedSupport.js @@ -1,8 +1,32 @@ /* global process */ +import { makeHelpers } from '@agoric/deploy-script-support'; +import { M, mustMatch } from '@agoric/store'; import { DEFAULT_CONTRACT_TERMS } from '../inter-protocol/price-feed-core.js'; -const { Fail } = assert; +/** @import {TypedPattern} from '@agoric/internal'; */ +/** + * @typedef {{ + * AGORIC_INSTANCE_NAME: string, + * ORACLE_ADDRESSES: string[], + * IN_BRAND_LOOKUP?: string[], + * IN_BRAND_NAME?: string, + * } & ({ IN_BRAND_LOOKUP: string[] } + * | { IN_BRAND_NAME: string }) + * } PriceFeedOptions + */ + +/** @type {TypedPattern} */ +const PriceFeedOptionsShape = M.and( + M.splitRecord({ + AGORIC_INSTANCE_NAME: M.string(), + ORACLE_ADDRESSES: M.arrayOf(M.string()), + }), + M.or( + M.splitRecord({ IN_BRAND_LOOKUP: M.arrayOf(M.string()) }), + M.splitRecord({ IN_BRAND_NAME: M.string() }), + ), +); /** * modified copy of ../inter-protocol/price-feed-core.js @@ -13,22 +37,16 @@ export const strictPriceFeedProposalBuilder = async ( { publishRef, install }, options, ) => { + mustMatch(options, PriceFeedOptionsShape); const { AGORIC_INSTANCE_NAME, IN_BRAND_LOOKUP, + // @ts-expect-error yes, IN_BRAND_LOOKUP may be undefined, but then IN_BRAND_LOOKUP must be defined IN_BRAND_NAME = IN_BRAND_LOOKUP[IN_BRAND_LOOKUP.length - 1], ORACLE_ADDRESSES, } = options; const oracleAddresses = ORACLE_ADDRESSES; - Array.isArray(oracleAddresses) || - Fail`ORACLE_ADDRESSES array is required; got ${oracleAddresses}`; - - AGORIC_INSTANCE_NAME || - Fail`AGORIC_INSTANCE_NAME is required; got ${AGORIC_INSTANCE_NAME}`; - - Array.isArray(IN_BRAND_LOOKUP) || - Fail`IN_BRAND_NAME array is required; got ${IN_BRAND_LOOKUP}`; return harden({ sourceSpec: '@agoric/inter-protocol/src/proposals/price-feed-proposal.js', @@ -88,3 +106,20 @@ export const deprecatedPriceFeedProposalBuilder = async (powers, options) => { * @type {import('@agoric/deploy-script-support/src/externalTypes.js').CoreEvalBuilder} */ export const priceFeedProposalBuilder = deprecatedPriceFeedProposalBuilder; + +export default async (homeP, endowments) => { + const { writeCoreEval } = await makeHelpers(homeP, endowments); + + const { scriptArgs } = endowments; + if (scriptArgs.length !== 1) throw RangeError('arg 0 must be JSON opts'); + let opts; + try { + opts = JSON.parse(scriptArgs[0]); + } catch (cause) { + throw RangeError('expected JSON', { cause }); + } + + await writeCoreEval('atomPriceFeed', powers => + strictPriceFeedProposalBuilder(powers, opts), + ); +}; From 95059af2e676fcd30849d69393c317084ded82d3 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Mon, 9 Sep 2024 13:59:39 -0500 Subject: [PATCH 09/21] test: liquidation works with new price feeds, vaults, auctions --- .../bootstrapTests/price-feed-replace.test.ts | 194 ++++++++++++++++++ 1 file changed, 194 insertions(+) create mode 100644 packages/boot/test/bootstrapTests/price-feed-replace.test.ts diff --git a/packages/boot/test/bootstrapTests/price-feed-replace.test.ts b/packages/boot/test/bootstrapTests/price-feed-replace.test.ts new file mode 100644 index 00000000000..53be53ec82f --- /dev/null +++ b/packages/boot/test/bootstrapTests/price-feed-replace.test.ts @@ -0,0 +1,194 @@ +/** + * @file The goal of this test is to see that the + * upgrade scripts re-wire all the contracts so new auctions and + * price feeds are connected to vaults correctly. + * + * 1. enter a bid + * 2. force prices to drop so a vault liquidates + * 3. verify that the bidder gets the liquidated assets. + */ +import { test as anyTest } from '@agoric/zoe/tools/prepare-test-env-ava.js'; + +import type { TestFn } from 'ava'; +import { ScheduleNotification } from '@agoric/inter-protocol/src/auction/scheduler.js'; +import { NonNullish } from '@agoric/internal'; +import { eventLoopIteration } from '@agoric/internal/src/testing-utils.js'; +import { + LiquidationTestContext, + likePayouts, + makeLiquidationTestContext, + scale6, + LiquidationSetup, + atomConfig, +} from '../../tools/liquidation.js'; + +const test = anyTest as TestFn; +test.before( + async t => + (t.context = await makeLiquidationTestContext(t, { env: process.env })), +); +test.after.always(t => t.context.shutdown()); + +const collateralBrandKey = 'ATOM'; +const managerIndex = 0; + +const setup: LiquidationSetup = { + vaults: [{ atom: 15, ist: 100, debt: 100.5 }], + bids: [{ give: '20IST', discount: 0.1 }], + price: { + starting: 12.34, + trigger: 9.99, + }, + auction: { + start: { collateral: 15, debt: 100.5 }, + end: { collateral: 9.659301, debt: 0 }, + }, +}; + +const outcome = { + bids: [{ payouts: { Bid: 0, Collateral: 1.800828 } }], +}; + +test.serial('setupVaults; run replace-price-feeds proposals', async t => { + const { + agoricNamesRemotes, + buildProposal, + evalProposal, + priceFeedDrivers, + refreshAgoricNamesRemotes, + setupVaults, + } = t.context; + + await setupVaults(collateralBrandKey, managerIndex, setup); + + const instancePre = agoricNamesRemotes.instance['ATOM-USD price feed']; + + const perFeedBuilder = '@agoric/builders/scripts/vats/priceFeedSupport.js'; + t.log('building', perFeedBuilder); + const brandName = collateralBrandKey; + const opts = { + AGORIC_INSTANCE_NAME: `${brandName}-USD price feed`, + ORACLE_ADDRESSES: atomConfig.oracleAddresses, + IN_BRAND_LOOKUP: ['agoricNames', 'oracleBrand', brandName], + IN_BRAND_DECIMALS: 6, + OUT_BRAND_LOOKUP: ['agoricNames', 'oracleBrand', 'USD'], + OUT_BRAND_DECIMALS: 4, + }; + + t.log('building all relevant CoreEvals'); + const coreEvals = await Promise.all([ + buildProposal(perFeedBuilder, opts), + // '@agoric/builders/scripts/vats/upgradeScaledPriceAuthorities.js', + buildProposal('@agoric/builders/scripts/vats/upgradeVaults.js'), + buildProposal('@agoric/builders/scripts/vats/add-auction.js'), + ]); + const combined = { + evals: coreEvals.flatMap(e => e.evals), + bundles: coreEvals.flatMap(e => e.bundles), + }; + t.log('evaluating', coreEvals.length, 'scripts'); + await evalProposal(combined); + + refreshAgoricNamesRemotes(); + const instancePost = agoricNamesRemotes.instance['ATOM-USD price feed']; + t.not(instancePre, instancePost); + + await priceFeedDrivers[collateralBrandKey].refreshInvitations(); +}); + +test.serial('1. place bid', async t => { + const { placeBids, readLatest } = t.context; + await placeBids(collateralBrandKey, 'agoric1buyer', setup, 0); + + t.like(readLatest('published.wallet.agoric1buyer.current'), { + liveOffers: [['ATOM-bid1', { id: 'ATOM-bid1' }]], + }); +}); + +test.serial('2. trigger liquidation by changing price', async t => { + const { priceFeedDrivers, readLatest } = t.context; + + await priceFeedDrivers[collateralBrandKey].setPrice(9.99); + + t.log(readLatest('published.priceFeed.ATOM-USD_price_feed'), { + // aka 9.99 + amountIn: { value: 1000000n }, + amountOut: { value: 9990000n }, + }); + + // check nothing liquidating yet + const liveSchedule: ScheduleNotification = readLatest( + 'published.auction.schedule', + ); + t.is(liveSchedule.activeStartTime, null); + const metricsPath = `published.vaultFactory.managers.manager${managerIndex}.metrics`; + + t.like(readLatest(metricsPath), { + numActiveVaults: setup.vaults.length, + numLiquidatingVaults: 0, + }); +}); + +test.serial('3. verify liquidation', async t => { + const { advanceTimeBy, advanceTimeTo, readLatest } = t.context; + + const liveSchedule: ScheduleNotification = readLatest( + 'published.auction.schedule', + ); + const metricsPath = `published.vaultFactory.managers.manager${managerIndex}.metrics`; + + // advance time to start an auction + console.log(collateralBrandKey, 'step 1 of 10'); + await advanceTimeTo(NonNullish(liveSchedule.nextDescendingStepTime)); + await eventLoopIteration(); // let promises to update vstorage settle + + // vaultFactory sent collateral for liquidation + t.like(readLatest(metricsPath), { + numActiveVaults: 0, + numLiquidatingVaults: setup.vaults.length, + liquidatingCollateral: { + value: scale6(setup.auction.start.collateral), + }, + liquidatingDebt: { value: scale6(setup.auction.start.debt) }, + lockedQuote: null, + }); + + console.log(collateralBrandKey, 'step 2 of 10'); + await advanceTimeBy(3, 'minutes'); + t.like(readLatest(`published.auction.book${managerIndex}`), { + collateralAvailable: { value: scale6(setup.auction.start.collateral) }, + startCollateral: { value: scale6(setup.auction.start.collateral) }, + startProceedsGoal: { value: scale6(setup.auction.start.debt) }, + }); + + console.log(collateralBrandKey, 'step 3 of 10'); + await advanceTimeBy(3, 'minutes'); + + console.log(collateralBrandKey, 'step 4 of 10'); + await advanceTimeBy(3, 'minutes'); + + console.log(collateralBrandKey, 'step 5 of 10'); + await advanceTimeBy(3, 'minutes'); + + console.log(collateralBrandKey, 'step 6 of 10'); + await advanceTimeBy(3, 'minutes'); + t.like(readLatest(`published.auction.book${managerIndex}`), { + collateralAvailable: { value: 13199172n }, + }); + + console.log(collateralBrandKey, 'step 7 of 10'); + await advanceTimeBy(3, 'minutes'); + + console.log(collateralBrandKey, 'step 8 of 10'); + await advanceTimeBy(3, 'minutes'); + + console.log(collateralBrandKey, 'step 9 of 10'); + await advanceTimeBy(3, 'minutes'); + + t.like(readLatest('published.wallet.agoric1buyer'), { + status: { + id: `${collateralBrandKey}-bid1`, + payouts: likePayouts(outcome.bids[0].payouts), + }, + }); +}); From 5e55342850f4a71b8c7df09ac0202ce21f308353 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Mon, 9 Sep 2024 17:23:43 -0500 Subject: [PATCH 10/21] chore(vats): include priceAggregator in WellKnownContracts --- packages/vats/src/core/types-ambient.d.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/vats/src/core/types-ambient.d.ts b/packages/vats/src/core/types-ambient.d.ts index f87f529db5a..d10010d8b5e 100644 --- a/packages/vats/src/core/types-ambient.d.ts +++ b/packages/vats/src/core/types-ambient.d.ts @@ -230,6 +230,7 @@ type WellKnownContracts = { mintHolder: typeof import('@agoric/vats/src/mintHolder.js').start; psm: typeof import('@agoric/inter-protocol/src/psm/psm.js').start; provisionPool: typeof import('@agoric/inter-protocol/src/provisionPool.js').start; + priceAggregator: typeof import('@agoric/inter-protocol/src/price/fluxAggregatorContract.js').start; reserve: typeof import('@agoric/inter-protocol/src/reserve/assetReserve.js').start; VaultFactory: typeof import('@agoric/inter-protocol/src/vaultFactory/vaultFactory.js').start; // no typeof because walletFactory is exporting `start` as a type From 20f82af878016086ed7c79353f1ad682ca961b1b Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Mon, 9 Sep 2024 19:28:10 -0500 Subject: [PATCH 11/21] refactor: sanitizePathSegment --- packages/inter-protocol/src/proposals/utils.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/inter-protocol/src/proposals/utils.js b/packages/inter-protocol/src/proposals/utils.js index f0c00e9e18e..c191824468f 100644 --- a/packages/inter-protocol/src/proposals/utils.js +++ b/packages/inter-protocol/src/proposals/utils.js @@ -2,6 +2,7 @@ import { Fail } from '@endo/errors'; import { E } from '@endo/far'; import { WalletName } from '@agoric/internal'; import { getCopyMapEntries, makeCopyMap } from '@agoric/store'; +import { assertPathSegment } from '@agoric/internal/src/lib-chainStorage.js'; /** @import {CopyMap} from '@endo/patterns'; */ @@ -163,3 +164,10 @@ export const oracleBrandFeedName = (inBrandName, outBrandName) => export const scaledPriceFeedName = issuerName => `scaledPriceAuthority-${issuerName}`; + +/** @type {(name: string) => string} */ +export const sanitizePathSegment = name => { + const candidate = name.replace(/ /g, '_'); + assertPathSegment(candidate); + return candidate; +}; From 3f2a9835880a3daeccf86f7e2796f9649c8db866 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Mon, 9 Sep 2024 19:29:05 -0500 Subject: [PATCH 12/21] chore: deploy-price-feeds core-eval script --- .../src/proposals/deploy-price-feeds.js | 314 ++++++++++++++++++ 1 file changed, 314 insertions(+) create mode 100644 packages/inter-protocol/src/proposals/deploy-price-feeds.js diff --git a/packages/inter-protocol/src/proposals/deploy-price-feeds.js b/packages/inter-protocol/src/proposals/deploy-price-feeds.js new file mode 100644 index 00000000000..f875019f82d --- /dev/null +++ b/packages/inter-protocol/src/proposals/deploy-price-feeds.js @@ -0,0 +1,314 @@ +import { makeTracer } from '@agoric/internal'; +import { makeStorageNodeChild } from '@agoric/internal/src/lib-chainStorage.js'; +import { E } from '@endo/far'; + +import { unitAmount } from '@agoric/zoe/src/contractSupport/priceQuote.js'; +import { + oracleBrandFeedName, + reserveThenDeposit, + sanitizePathSegment, +} from './utils.js'; +import { upgradeScaledPriceAuthorities } from './upgrade-scaledPriceAuthorities.js'; + +const STORAGE_PATH = 'priceFeed'; + +/** @type {ChainlinkConfig} */ +export const DEFAULT_CONTRACT_TERMS = { + maxSubmissionCount: 1000, + minSubmissionCount: 2, + restartDelay: 1n, // the number of rounds an Oracle has to wait before they can initiate another round + timeout: 10, // in seconds according to chainTimerService + minSubmissionValue: 1, + maxSubmissionValue: 2 ** 256, +}; + +/** @import {EconomyBootstrapPowers} from './econ-behaviors.js'; */ +/** @import {ChainlinkConfig} from '@agoric/inter-protocol/src/price/fluxAggregatorKit.js'; */ +/** @typedef {typeof import('@agoric/inter-protocol/src/price/fluxAggregatorContract.js').start} FluxStartFn */ + +const trace = makeTracer('RunPriceFeed', true); + +/** + * @typedef {{ + * oracleAddresses: string[]; + * inBrandNames: string[]; + * contractTerms?: Partial; + * }} PriceFeedConfig + */ + +/** + * @param {EconomyBootstrapPowers} powers + * @param {string} bundleID + */ +const installPriceAggregator = async ( + { + consume: { zoe }, + installation: { + produce: { priceAggregator }, + }, + }, + bundleID, +) => { + /** @type {Installation} */ + const installation = await E(zoe).installBundleID(bundleID); + priceAggregator.reset(); + priceAggregator.resolve(installation); + trace('installed priceAggregator', bundleID.slice(0, 'b1-1234567'.length)); + return installation; +}; + +/** + * Create inert brands (no mint or issuer) referred to by price oracles. + * + * @param {EconomyBootstrapPowers & NamedVatPowers} space + * @param {{ name: string; decimalPlaces: number }} opt + * @returns {Promise>} + */ +export const ensureOracleBrand = async ( + { + namedVat: { + consume: { agoricNames }, + }, + oracleBrand: { produce: oracleBrandProduce }, + }, + { name, decimalPlaces }, +) => { + const brand = E(agoricNames).provideInertBrand(name, { + assetKind: 'nat', + decimalPlaces, + }); + + oracleBrandProduce[name].reset(); + oracleBrandProduce[name].resolve(brand); + return brand; +}; + +/** + * @param {EconomyBootstrapPowers} powers + * @param {{ + * AGORIC_INSTANCE_NAME: string; + * contractTerms: import('@agoric/inter-protocol/src/price/fluxAggregatorKit.js').ChainlinkConfig; + * brandIn: Brand<'nat'>; + * brandOut: Brand<'nat'>; + * }} config + * @param {Installation} installation + */ +const startPriceAggegatorInstance = async ( + { + consume: { + board, + chainStorage, + chainTimerService, + econCharterKit, + highPrioritySendersManager, + namesByAddressAdmin, + startGovernedUpgradable, + }, + instance: { produce: produceInstance }, + }, + { AGORIC_INSTANCE_NAME, contractTerms, brandIn, brandOut }, + installation, +) => { + trace('startPriceAggegatorInstance', AGORIC_INSTANCE_NAME); + const label = sanitizePathSegment(AGORIC_INSTANCE_NAME); + + const feedsStorage = await makeStorageNodeChild(chainStorage, STORAGE_PATH); + const storageNode = await E(feedsStorage).makeChildNode(label); + const marshaller = await E(board).getReadonlyMarshaller(); + + const terms = harden({ + ...contractTerms, + description: AGORIC_INSTANCE_NAME, + brandIn, + brandOut, + timer: await chainTimerService, + unitAmountIn: await unitAmount(brandIn), + }); + const privateArgs = { + highPrioritySendersManager: await highPrioritySendersManager, + marshaller, + namesByAddressAdmin, + storageNode, + }; + const governedKit = await E(startGovernedUpgradable)({ + governedParams: {}, + privateArgs, + terms, + label, + // @ts-expect-error GovernableStartFn vs. fluxAggregatorContract.js start + installation, + }); + produceInstance[AGORIC_INSTANCE_NAME].reset(); + produceInstance[AGORIC_INSTANCE_NAME].resolve(governedKit.instance); + trace( + 'new instance', + label, + { terms, privateArgs, installation }, + governedKit, + ); + + await E(E.get(econCharterKit).creatorFacet).addInstance( + governedKit.instance, + governedKit.governorCreatorFacet, + AGORIC_INSTANCE_NAME, + ); + trace('added', label, 'instance to econCharter'); + + /** @type {import('@agoric/zoe/src/zoeService/utils.js').StartedInstanceKit} */ + // @ts-expect-error + const { instance, publicFacet, creatorFacet } = governedKit; + + return harden({ instance, publicFacet, creatorFacet }); +}; + +/** + * Send invitations to oracle operators for a price feed. + * + * @param {EconomyBootstrapPowers} powers + * @param {{ oracleAddresses: string[]; AGORIC_INSTANCE_NAME: string }} config + * @param {any} creatorFacet + */ +const distributeInvitations = async ( + { consume: { namesByAddressAdmin } }, + { oracleAddresses, AGORIC_INSTANCE_NAME }, + creatorFacet, +) => { + /** @param {string} addr */ + const addOracle = async addr => { + const invitation = await E(creatorFacet).makeOracleInvitation(addr); + const debugName = `${AGORIC_INSTANCE_NAME} member ${addr}`; + await reserveThenDeposit(debugName, namesByAddressAdmin, addr, [ + invitation, + ]).catch(err => console.error(`failed deposit to ${debugName}`, err)); + }; + + trace('distributing invitations', oracleAddresses); + // This doesn't resolve until oracle operators create their smart wallets. + // Don't block bootstrap on it. + void Promise.all(oracleAddresses.map(addOracle)); + trace('createPriceFeed complete'); +}; + +/** + * @param {EconomyBootstrapPowers & NamedVatPowers} powers + * @param {{ + * options: { + * priceFeedOptions: PriceFeedConfig & { + * priceAggregatorRef: { bundleID: string }; + * scaledPARef: { bundleID: string }; + * inBrandsDecimals?: number; + * contractTerms?: ChainlinkConfig; + * outBrandName?: string; + * outBrandDecimals?: number; + * }; + * }; + * }} config + */ +export const deployPriceFeeds = async (powers, config) => { + const { + inBrandNames, + oracleAddresses, + contractTerms, + priceAggregatorRef, + scaledPARef, + inBrandsDecimals = 6, + outBrandName = 'USD', + outBrandDecimals = 6, + } = config.options.priceFeedOptions; + await null; + + const installation = await installPriceAggregator( + powers, + priceAggregatorRef.bundleID, + ); + + const { + consume: { priceAuthorityAdmin }, + } = powers; + for (const inBrandName of inBrandNames) { + const AGORIC_INSTANCE_NAME = oracleBrandFeedName(inBrandName, outBrandName); + const brandIn = await ensureOracleBrand(powers, { + name: inBrandName, + decimalPlaces: inBrandsDecimals, + }); + const brandOut = await ensureOracleBrand(powers, { + name: outBrandName, + decimalPlaces: outBrandDecimals, + }); + const kit = await startPriceAggegatorInstance( + powers, + { + AGORIC_INSTANCE_NAME, + brandIn, + brandOut, + contractTerms: { ...DEFAULT_CONTRACT_TERMS, ...contractTerms }, + }, + installation, + ); + + const forceReplace = true; + await E(priceAuthorityAdmin).registerPriceAuthority( + E(kit.publicFacet).getPriceAuthority(), + brandIn, + brandOut, + forceReplace, + ); + + await distributeInvitations( + powers, + { oracleAddresses, AGORIC_INSTANCE_NAME }, + kit.creatorFacet, + ); + } + + await upgradeScaledPriceAuthorities(powers, { + options: { scaledPARef }, + }); +}; + +const t = 'priceFeed'; + +/** + * Add a price feed to a running chain, returning the manifest, installations, + * and options. + * + * @param {object} utils + * @param {any} utils.restoreRef + * @param {PriceFeedConfig & { priceAggregatorRef: any }} priceFeedOptions + */ +export const getManifestForPriceFeeds = async ( + { restoreRef }, + priceFeedOptions, +) => ({ + manifest: { + [deployPriceFeeds.name]: { + consume: { + agoricNamesAdmin: t, + board: t, + chainStorage: t, + chainTimerService: t, + econCharterKit: t, + highPrioritySendersManager: t, + namesByAddressAdmin: t, + priceAuthority: t, + priceAuthorityAdmin: t, + startGovernedUpgradable: t, + zoe: t, + }, + instance: { + produce: t, + }, + }, + }, + installations: { + // ??? will every eval of price-feed-proposal install priceAggregator ? + priceAggregator: restoreRef(priceFeedOptions.priceAggregatorRef), + }, + options: { + priceFeedOptions: { + oracleAddresses: priceFeedOptions.oracleAddresses, + inBrandNames: priceFeedOptions.inBrandNames, + priceAggregatorRef: priceFeedOptions.priceAggregatorRef, + }, + }, +}); From e618b56be16ad8c4b8c88b53150455b473800484 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Mon, 9 Sep 2024 19:29:23 -0500 Subject: [PATCH 13/21] chore: integrated price feed proposal --- .../inter-protocol/updatePriceFeeds.js | 66 ++++++++++++------- 1 file changed, 43 insertions(+), 23 deletions(-) diff --git a/packages/builders/scripts/inter-protocol/updatePriceFeeds.js b/packages/builders/scripts/inter-protocol/updatePriceFeeds.js index d3b22f57dae..746128fe909 100644 --- a/packages/builders/scripts/inter-protocol/updatePriceFeeds.js +++ b/packages/builders/scripts/inter-protocol/updatePriceFeeds.js @@ -1,6 +1,9 @@ import { makeHelpers } from '@agoric/deploy-script-support'; -import { strictPriceFeedProposalBuilder } from '../vats/priceFeedSupport.js'; +import { getManifestForPriceFeeds } from '@agoric/inter-protocol/src/proposals/deploy-price-feeds.js'; +/** @import {PriceFeedConfig} from '@agoric/inter-protocol/src/proposals/deploy-price-feeds.js'; */ + +/** @type {Record} */ const configurations = { UNRELEASED_A3P_INTEGRATION: { oracleAddresses: [ @@ -18,7 +21,8 @@ const configurations = { 'agoric1krunjcqfrf7la48zrvdfeeqtls5r00ep68mzkr', // Simply Staking 'agoric1n4fcxsnkxe4gj6e24naec99hzmc4pjfdccy5nj', // P2P ], - inBrandName: ['ATOM', 'stATOM', 'stOSMO', 'stTIA', 'stkATOM'], + inBrandNames: ['ATOM', 'stATOM', 'stOSMO', 'stTIA', 'stkATOM'], + contractTerms: { minSubmissionCount: 3 }, }, UNRELEASED_devnet: { oracleAddresses: [ @@ -32,30 +36,46 @@ const configurations = { }, }; -export default async (homeP, endowments) => { - const upgradeEnvironment = endowments.scriptArgs?.[0]; - console.log('UPPrices', upgradeEnvironment); +/** @type {import('@agoric/deploy-script-support/src/externalTypes.js').CoreEvalBuilder} */ +export const defaultProposalBuilder = async ({ publishRef, install }, opts) => { + return harden({ + sourceSpec: '@agoric/inter-protocol/src/proposals/deploy-price-feeds.js', + getManifestCall: [ + getManifestForPriceFeeds.name, + { + ...opts, + priceAggregatorRef: publishRef( + install( + '@agoric/inter-protocol/src/price/fluxAggregatorContract.js', + '../bundles/bundle-fluxAggregatorKit.js', + ), + ), + scaledPARef: publishRef( + install( + '@agoric/zoe/src/contracts/scaledPriceAuthority.js', + '../bundles/bundle-scaledPriceAuthority.js', + ), + ), + }, + ], + }); +}; - const { writeCoreEval } = await makeHelpers(homeP, endowments); +const { keys } = Object; +const Usage = `agoric run updatePriceFeed.js ${keys(configurations).join(' | ')}`; - const coreEvalSteps = []; - for (const config of configurations[upgradeEnvironment]) { - const { inBrandNames, oracleAddresses } = config; - for (const inBrandName of inBrandNames) { - const options = { - AGORIC_INSTANCE_NAME: `${inBrandName}-USD price feed`, - ORACLE_ADDRESSES: oracleAddresses, - IN_BRAND_LOOKUP: ['agoricNames', 'oracleBrand', inBrandName], - }; - coreEvalSteps.push( - writeCoreEval(options.AGORIC_INSTANCE_NAME, opts => - strictPriceFeedProposalBuilder({ ...opts, ...options }), - ), - ); - } +export default async (homeP, endowments) => { + const { scriptArgs } = endowments; + const config = configurations[scriptArgs?.[0]]; + if (!config) { + console.error(Usage); + process.exit(1); } + console.log('UPPrices', scriptArgs, config); - // TODO(hibbert) leave a marker in promise space as a signal to vaults upgrade + const { writeCoreEval } = await makeHelpers(homeP, endowments); - await Promise.all(coreEvalSteps); + await writeCoreEval('gov-price-feeds', (utils, opts) => + defaultProposalBuilder(utils, { ...opts, ...config }), + ); }; From 52698567d0b1b8f6f5f2fef9eada13b672a01c72 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Mon, 9 Sep 2024 19:29:35 -0500 Subject: [PATCH 14/21] test: use updatePriceFeeds --- packages/boot/test/bootstrapTests/price-feed-replace.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/boot/test/bootstrapTests/price-feed-replace.test.ts b/packages/boot/test/bootstrapTests/price-feed-replace.test.ts index 53be53ec82f..e2cc8895871 100644 --- a/packages/boot/test/bootstrapTests/price-feed-replace.test.ts +++ b/packages/boot/test/bootstrapTests/price-feed-replace.test.ts @@ -63,7 +63,8 @@ test.serial('setupVaults; run replace-price-feeds proposals', async t => { const instancePre = agoricNamesRemotes.instance['ATOM-USD price feed']; - const perFeedBuilder = '@agoric/builders/scripts/vats/priceFeedSupport.js'; + const perFeedBuilder = + '@agoric/builders/scripts/inter-protocol/updatePriceFeeds.js'; t.log('building', perFeedBuilder); const brandName = collateralBrandKey; const opts = { From 13843ebe6746f352bedf3f978aa89f797cee8907 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Mon, 9 Sep 2024 20:00:23 -0500 Subject: [PATCH 15/21] fixup! feat: makeProposalExtractor supports cliArgs --- packages/boot/tools/supports.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/boot/tools/supports.ts b/packages/boot/tools/supports.ts index 3ebfca50da2..6b62a9b287e 100644 --- a/packages/boot/tools/supports.ts +++ b/packages/boot/tools/supports.ts @@ -194,15 +194,11 @@ export const makeProposalExtractor = ({ childProcess, fs }: Powers) => { return { evals, bundles }; }; - const buildAndExtract = async ( - builderPath: string, - opts?: Record, - ) => { + const buildAndExtract = async (builderPath: string, args: string[] = []) => { const tmpDir = await fsAmbientPromises.mkdtemp( join(getPkgPath('builders'), 'proposal-'), ); - const args = opts ? [JSON.stringify(opts)] : []; const built = parseProposalParts( runPackageScript( tmpDir, From 3e5378d3cd69da653fb50730f14db253aecce4c0 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Mon, 9 Sep 2024 20:00:57 -0500 Subject: [PATCH 16/21] test: use integrated updatePriceFeeds with config --- .../bootstrapTests/price-feed-replace.test.ts | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/packages/boot/test/bootstrapTests/price-feed-replace.test.ts b/packages/boot/test/bootstrapTests/price-feed-replace.test.ts index e2cc8895871..fa6f9a052eb 100644 --- a/packages/boot/test/bootstrapTests/price-feed-replace.test.ts +++ b/packages/boot/test/bootstrapTests/price-feed-replace.test.ts @@ -63,23 +63,14 @@ test.serial('setupVaults; run replace-price-feeds proposals', async t => { const instancePre = agoricNamesRemotes.instance['ATOM-USD price feed']; - const perFeedBuilder = + const priceFeedBuilder = '@agoric/builders/scripts/inter-protocol/updatePriceFeeds.js'; - t.log('building', perFeedBuilder); + t.log('building', priceFeedBuilder); const brandName = collateralBrandKey; - const opts = { - AGORIC_INSTANCE_NAME: `${brandName}-USD price feed`, - ORACLE_ADDRESSES: atomConfig.oracleAddresses, - IN_BRAND_LOOKUP: ['agoricNames', 'oracleBrand', brandName], - IN_BRAND_DECIMALS: 6, - OUT_BRAND_LOOKUP: ['agoricNames', 'oracleBrand', 'USD'], - OUT_BRAND_DECIMALS: 4, - }; t.log('building all relevant CoreEvals'); const coreEvals = await Promise.all([ - buildProposal(perFeedBuilder, opts), - // '@agoric/builders/scripts/vats/upgradeScaledPriceAuthorities.js', + buildProposal(priceFeedBuilder, ['UNRELEASED_A3P_INTEGRATION']), buildProposal('@agoric/builders/scripts/vats/upgradeVaults.js'), buildProposal('@agoric/builders/scripts/vats/add-auction.js'), ]); From a9117e9b5d3bd02402f9d6b9ca46969c8ff5c264 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Mon, 9 Sep 2024 20:01:40 -0500 Subject: [PATCH 17/21] chore: align options between deploy-price-feed and scaled... --- .../src/proposals/deploy-price-feeds.js | 38 ++++++++----------- 1 file changed, 15 insertions(+), 23 deletions(-) diff --git a/packages/inter-protocol/src/proposals/deploy-price-feeds.js b/packages/inter-protocol/src/proposals/deploy-price-feeds.js index f875019f82d..5bc1d88f2c9 100644 --- a/packages/inter-protocol/src/proposals/deploy-price-feeds.js +++ b/packages/inter-protocol/src/proposals/deploy-price-feeds.js @@ -192,15 +192,13 @@ const distributeInvitations = async ( /** * @param {EconomyBootstrapPowers & NamedVatPowers} powers * @param {{ - * options: { - * priceFeedOptions: PriceFeedConfig & { - * priceAggregatorRef: { bundleID: string }; - * scaledPARef: { bundleID: string }; - * inBrandsDecimals?: number; - * contractTerms?: ChainlinkConfig; - * outBrandName?: string; - * outBrandDecimals?: number; - * }; + * options: PriceFeedConfig & { + * priceAggregatorRef: { bundleID: string }; + * scaledPARef: { bundleID: string }; + * inBrandsDecimals?: number; + * contractTerms?: ChainlinkConfig; + * outBrandName?: string; + * outBrandDecimals?: number; * }; * }} config */ @@ -214,7 +212,7 @@ export const deployPriceFeeds = async (powers, config) => { inBrandsDecimals = 6, outBrandName = 'USD', outBrandDecimals = 6, - } = config.options.priceFeedOptions; + } = config.options; await null; const installation = await installPriceAggregator( @@ -269,8 +267,7 @@ export const deployPriceFeeds = async (powers, config) => { const t = 'priceFeed'; /** - * Add a price feed to a running chain, returning the manifest, installations, - * and options. + * Thread price feed upgrade options through from builder to core-eval. * * @param {object} utils * @param {any} utils.restoreRef @@ -282,33 +279,28 @@ export const getManifestForPriceFeeds = async ( ) => ({ manifest: { [deployPriceFeeds.name]: { + namedVat: t, consume: { agoricNamesAdmin: t, board: t, chainStorage: t, chainTimerService: t, + contractKits: t, econCharterKit: t, highPrioritySendersManager: t, + instancePrivateArgs: t, namesByAddressAdmin: t, priceAuthority: t, priceAuthorityAdmin: t, startGovernedUpgradable: t, zoe: t, }, + installation: { produce: { priceAggregator: t } }, instance: { produce: t, }, + oracleBrand: { produce: t }, }, }, - installations: { - // ??? will every eval of price-feed-proposal install priceAggregator ? - priceAggregator: restoreRef(priceFeedOptions.priceAggregatorRef), - }, - options: { - priceFeedOptions: { - oracleAddresses: priceFeedOptions.oracleAddresses, - inBrandNames: priceFeedOptions.inBrandNames, - priceAggregatorRef: priceFeedOptions.priceAggregatorRef, - }, - }, + options: { ...priceFeedOptions }, }); From 6f6c56bbd559ab08e00351ad144e0855cf040921 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Mon, 9 Sep 2024 20:02:18 -0500 Subject: [PATCH 18/21] chore: prune priceFeedSupport.js --- .../builders/scripts/vats/priceFeedSupport.js | 125 ------------------ 1 file changed, 125 deletions(-) delete mode 100644 packages/builders/scripts/vats/priceFeedSupport.js diff --git a/packages/builders/scripts/vats/priceFeedSupport.js b/packages/builders/scripts/vats/priceFeedSupport.js deleted file mode 100644 index 8a975bfdcc0..00000000000 --- a/packages/builders/scripts/vats/priceFeedSupport.js +++ /dev/null @@ -1,125 +0,0 @@ -/* global process */ - -import { makeHelpers } from '@agoric/deploy-script-support'; -import { M, mustMatch } from '@agoric/store'; -import { DEFAULT_CONTRACT_TERMS } from '../inter-protocol/price-feed-core.js'; - -/** @import {TypedPattern} from '@agoric/internal'; */ -/** - * @typedef {{ - * AGORIC_INSTANCE_NAME: string, - * ORACLE_ADDRESSES: string[], - * IN_BRAND_LOOKUP?: string[], - * IN_BRAND_NAME?: string, - * } & ({ IN_BRAND_LOOKUP: string[] } - * | { IN_BRAND_NAME: string }) - * } PriceFeedOptions - */ - -/** @type {TypedPattern} */ -const PriceFeedOptionsShape = M.and( - M.splitRecord({ - AGORIC_INSTANCE_NAME: M.string(), - ORACLE_ADDRESSES: M.arrayOf(M.string()), - }), - M.or( - M.splitRecord({ IN_BRAND_LOOKUP: M.arrayOf(M.string()) }), - M.splitRecord({ IN_BRAND_NAME: M.string() }), - ), -); - -/** - * modified copy of ../inter-protocol/price-feed-core.js - * - * @type {import('@agoric/deploy-script-support/src/externalTypes.js').CoreEvalBuilder} - */ -export const strictPriceFeedProposalBuilder = async ( - { publishRef, install }, - options, -) => { - mustMatch(options, PriceFeedOptionsShape); - const { - AGORIC_INSTANCE_NAME, - IN_BRAND_LOOKUP, - // @ts-expect-error yes, IN_BRAND_LOOKUP may be undefined, but then IN_BRAND_LOOKUP must be defined - IN_BRAND_NAME = IN_BRAND_LOOKUP[IN_BRAND_LOOKUP.length - 1], - ORACLE_ADDRESSES, - } = options; - - const oracleAddresses = ORACLE_ADDRESSES; - - return harden({ - sourceSpec: '@agoric/inter-protocol/src/proposals/price-feed-proposal.js', - getManifestCall: [ - 'getManifestForPriceFeed', - { - AGORIC_INSTANCE_NAME, - contractTerms: DEFAULT_CONTRACT_TERMS, - oracleAddresses, - IN_BRAND_NAME, - IN_BRAND_DECIMALS: 6, - OUT_BRAND_DECIMALS: 4, - OUT_BRAND_NAME: 'USD', - priceAggregatorRef: publishRef( - install( - '@agoric/inter-protocol/src/price/fluxAggregatorContract.js', - '../bundles/bundle-fluxAggregatorKit.js', - ), - ), - }, - ], - }); -}; - -/** - * @deprecated use `strictPriceFeedProposalBuilder` and specify arguments instead - * @type {import('@agoric/deploy-script-support/src/externalTypes.js').CoreEvalBuilder} - */ -export const deprecatedPriceFeedProposalBuilder = async (powers, options) => { - console.warn( - 'deprecated ambient `priceFeedProposalBuilder`; use `strictPriceFeedProposalBuilder` instead', - ); - - const DEFAULT_ORACLE_ADDRESSES = [ - // XXX These are the oracle addresses. They must be provided before the chain - // is running, which means they must be known ahead of time. - // see https://github.com/Agoric/agoric-3-proposals/issues/5 - 'agoric1lu9hh5vgx05hmlpfu47hukershgdxctk6l5s05', - 'agoric15lpnq2mjsdhtztf6khp7mrsq66hyrssspy92pd', - 'agoric1mwm224epc4l3pjcz7qsxnudcuktpynwkmnfqfp', - ]; - - const { GOV1ADDR, GOV2ADDR, GOV3ADDR } = process.env; - const governanceAddressEnv = [GOV1ADDR, GOV2ADDR, GOV3ADDR].filter(x => x); - const ORACLE_ADDRESSES = governanceAddressEnv.length - ? governanceAddressEnv - : DEFAULT_ORACLE_ADDRESSES; - - return strictPriceFeedProposalBuilder(powers, { - ...options, - ORACLE_ADDRESSES, - }); -}; - -/** - * @deprecated use `strictPriceFeedProposalBuilder` and specify arguments instead - * @type {import('@agoric/deploy-script-support/src/externalTypes.js').CoreEvalBuilder} - */ -export const priceFeedProposalBuilder = deprecatedPriceFeedProposalBuilder; - -export default async (homeP, endowments) => { - const { writeCoreEval } = await makeHelpers(homeP, endowments); - - const { scriptArgs } = endowments; - if (scriptArgs.length !== 1) throw RangeError('arg 0 must be JSON opts'); - let opts; - try { - opts = JSON.parse(scriptArgs[0]); - } catch (cause) { - throw RangeError('expected JSON', { cause }); - } - - await writeCoreEval('atomPriceFeed', powers => - strictPriceFeedProposalBuilder(powers, opts), - ); -}; From 710695b9e2ea10aeef242359c480d670638e3f5b Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Mon, 9 Sep 2024 20:22:57 -0500 Subject: [PATCH 19/21] fixup! test: use integrated updatePriceFeeds with config --- packages/boot/test/bootstrapTests/price-feed-replace.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/boot/test/bootstrapTests/price-feed-replace.test.ts b/packages/boot/test/bootstrapTests/price-feed-replace.test.ts index fa6f9a052eb..e3c60c5016c 100644 --- a/packages/boot/test/bootstrapTests/price-feed-replace.test.ts +++ b/packages/boot/test/bootstrapTests/price-feed-replace.test.ts @@ -70,7 +70,7 @@ test.serial('setupVaults; run replace-price-feeds proposals', async t => { t.log('building all relevant CoreEvals'); const coreEvals = await Promise.all([ - buildProposal(priceFeedBuilder, ['UNRELEASED_A3P_INTEGRATION']), + buildProposal(priceFeedBuilder, ['UNRELEASED_main']), buildProposal('@agoric/builders/scripts/vats/upgradeVaults.js'), buildProposal('@agoric/builders/scripts/vats/add-auction.js'), ]); From 3dc7a9e7bcdcc40bd0b83fbd105ed217e6e7ad81 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Mon, 9 Sep 2024 20:23:07 -0500 Subject: [PATCH 20/21] feat: produce priceAuthority8400 signal when price feeds are updated --- .../src/proposals/deploy-price-feeds.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/inter-protocol/src/proposals/deploy-price-feeds.js b/packages/inter-protocol/src/proposals/deploy-price-feeds.js index 5bc1d88f2c9..f0f9ab2e15a 100644 --- a/packages/inter-protocol/src/proposals/deploy-price-feeds.js +++ b/packages/inter-protocol/src/proposals/deploy-price-feeds.js @@ -220,9 +220,7 @@ export const deployPriceFeeds = async (powers, config) => { priceAggregatorRef.bundleID, ); - const { - consume: { priceAuthorityAdmin }, - } = powers; + const { priceAuthorityAdmin, priceAuthority } = powers.consume; for (const inBrandName of inBrandNames) { const AGORIC_INSTANCE_NAME = oracleBrandFeedName(inBrandName, outBrandName); const brandIn = await ensureOracleBrand(powers, { @@ -262,6 +260,10 @@ export const deployPriceFeeds = async (powers, config) => { await upgradeScaledPriceAuthorities(powers, { options: { scaledPARef }, }); + + // cf. #8400 QuotePayments storage leak + powers.produce.priceAuthority8400.resolve(priceAuthority); + powers.produce.priceAuthority.resolve(priceAuthority); }; const t = 'priceFeed'; @@ -300,6 +302,10 @@ export const getManifestForPriceFeeds = async ( produce: t, }, oracleBrand: { produce: t }, + produce: { + priceAuthority: t, + priceAuthority8400: t, + }, }, }, options: { ...priceFeedOptions }, From 3d6c4e7eddc72527f2ea533a74dba5f481ddd37a Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Tue, 10 Sep 2024 13:05:36 -0500 Subject: [PATCH 21/21] chore: wait for "price feeds all repaird" signal --- packages/inter-protocol/src/proposals/add-auction.js | 6 +++--- packages/inter-protocol/src/proposals/upgrade-vaults.js | 5 +++++ packages/vats/src/core/types-ambient.d.ts | 2 ++ 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/inter-protocol/src/proposals/add-auction.js b/packages/inter-protocol/src/proposals/add-auction.js index ca5bb004759..6bfad088ee6 100644 --- a/packages/inter-protocol/src/proposals/add-auction.js +++ b/packages/inter-protocol/src/proposals/add-auction.js @@ -27,7 +27,7 @@ export const addAuction = async ( chainTimerService, economicCommitteeCreatorFacet: electorateCreatorFacet, econCharterKit, - priceAuthority, + priceAuthority8400, zoe, }, produce: { auctioneerKit: produceAuctioneerKit, auctionUpgradeNewInstance }, @@ -95,7 +95,7 @@ export const addAuction = async ( const auctionTerms = makeGovernedATerms( { storageNode, marshaller }, chainTimerService, - priceAuthority, + priceAuthority8400, reservePublicFacet, { ...params, @@ -197,7 +197,7 @@ export const ADD_AUCTION_MANIFEST = harden({ chainTimerService: true, econCharterKit: true, economicCommitteeCreatorFacet: true, - priceAuthority: true, + priceAuthority8400: true, zoe: true, }, produce: { diff --git a/packages/inter-protocol/src/proposals/upgrade-vaults.js b/packages/inter-protocol/src/proposals/upgrade-vaults.js index 2088aac9e67..f98e7293ce2 100644 --- a/packages/inter-protocol/src/proposals/upgrade-vaults.js +++ b/packages/inter-protocol/src/proposals/upgrade-vaults.js @@ -27,6 +27,7 @@ export const upgradeVaults = async ( reserveKit, vaultFactoryKit, zoe, + priceAuthority8400, }, produce: { auctionUpgradeNewInstance: auctionUpgradeNewInstanceProducer }, installation: { @@ -44,6 +45,9 @@ export const upgradeVaults = async ( const allBrands = await E(zoe).getBrands(directorInstance); const { Minted: _istBrand, ...vaultBrands } = allBrands; + console.log('upgradeVaults awaiting priceAuthority8400'); + await priceAuthority8400; + const bundleID = vaultsRef.bundleID; console.log(`upgradeVaults: bundleId`, bundleID); /** @@ -200,6 +204,7 @@ export const getManifestForUpgradeVaults = async ( manifest: { [upgradeVaults.name]: { consume: { + priceAuthority8400: uV, auctionUpgradeNewInstance: uV, chainTimerService: uV, economicCommitteeCreatorFacet: uV, diff --git a/packages/vats/src/core/types-ambient.d.ts b/packages/vats/src/core/types-ambient.d.ts index d10010d8b5e..a4a88296125 100644 --- a/packages/vats/src/core/types-ambient.d.ts +++ b/packages/vats/src/core/types-ambient.d.ts @@ -378,6 +378,8 @@ type ChainBootstrapSpaceT = { powerStore: MapStore; priceAuthorityVat: Awaited; priceAuthority: import('@agoric/zoe/tools/types.js').PriceAuthority; + // signal that price feeds have #8400 QuotePayments storage leak fixed + priceAuthority8400: import('@agoric/zoe/tools/types.js').PriceAuthority; priceAuthorityAdmin: import('@agoric/vats/src/priceAuthorityRegistry').PriceAuthorityRegistryAdmin; provisioning: Awaited | undefined; provisionBridgeManager: