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/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/e:upgrade-next/agd-tools.js b/a3p-integration/proposals/f:replace-price-feeds/agd-tools.js similarity index 100% rename from a3p-integration/proposals/e:upgrade-next/agd-tools.js rename to a3p-integration/proposals/f:replace-price-feeds/agd-tools.js 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 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/n:upgrade-next/agd-tools.js b/a3p-integration/proposals/n:upgrade-next/agd-tools.js new file mode 100644 index 00000000000..550668e110b --- /dev/null +++ b/a3p-integration/proposals/n:upgrade-next/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/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 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(