diff --git a/packages/agoric-cli/src/commands/oracle.js b/packages/agoric-cli/src/commands/oracle.js index 2c458070e06..ccad184afbf 100644 --- a/packages/agoric-cli/src/commands/oracle.js +++ b/packages/agoric-cli/src/commands/oracle.js @@ -7,7 +7,7 @@ import { Nat } from '@endo/nat'; import { Command } from 'commander'; import * as cp from 'child_process'; import { inspect } from 'util'; -import { instanceNameFor } from '@agoric/inter-protocol/src/proposals/price-feed-proposal.js'; +import { oracleBrandFeedName } from '@agoric/inter-protocol/src/proposals/utils.js'; import { normalizeAddressWithOptions } from '../lib/chain.js'; import { getNetworkConfig, makeRpcUtils, storageHelper } from '../lib/rpc.js'; import { @@ -76,7 +76,7 @@ export const makeOracleCommand = (logger, io = {}) => { const utils = await makeRpcUtils({ fetch }); const lookupPriceAggregatorInstance = ([brandIn, brandOut]) => { - const name = instanceNameFor(brandIn, brandOut); + const name = oracleBrandFeedName(brandIn, brandOut); const instance = utils.agoricNames.instance[name]; if (!instance) { logger.debug('known instances:', utils.agoricNames.instance); diff --git a/packages/boot/test/bootstrapTests/drivers.ts b/packages/boot/test/bootstrapTests/drivers.ts index 66c7b9afb01..8b494a0af4a 100644 --- a/packages/boot/test/bootstrapTests/drivers.ts +++ b/packages/boot/test/bootstrapTests/drivers.ts @@ -7,7 +7,7 @@ import { FakeStorageKit, slotToRemotable, } from '@agoric/internal/src/storage-test-utils.js'; -import { instanceNameFor } from '@agoric/inter-protocol/src/proposals/price-feed-proposal.js'; +import { oracleBrandFeedName } from '@agoric/inter-protocol/src/proposals/utils.js'; import { AgoricNamesRemotes, @@ -147,7 +147,7 @@ export const makePriceFeedDriver = async ( walletFactoryDriver: WalletFactoryDriver, oracleAddresses: string[], ) => { - const priceFeedName = instanceNameFor(collateralBrandKey, 'USD'); + const priceFeedName = oracleBrandFeedName(collateralBrandKey, 'USD'); const oracleWallets = await Promise.all( oracleAddresses.map(addr => walletFactoryDriver.provideSmartWallet(addr)), diff --git a/packages/inter-protocol/src/price/README.md b/packages/inter-protocol/src/price/README.md index 2a93842341d..f26ab15d17c 100644 --- a/packages/inter-protocol/src/price/README.md +++ b/packages/inter-protocol/src/price/README.md @@ -4,6 +4,19 @@ This directory contains the `fluxAggregatorKit.js` contract which takes prices a input and outputs a best known price. There are multiple ways to get the price, including a PriceAuthority interface. +## Design + +The authorities are hierarchical. Many authorities can be registered in the priceAuthorityRegistry. + +For oracles, there must be two: the negotiable brand and the _oracle_ brand (an inert one). + +The intended flow is that: +1. a negotiable brand is created (e.g. ATOM) +2. a price provider says “i can give you quotes for that” and runs price-feed-proposal. That makes “oracleBrands” (which are inert and have a separate identity so that they don’t have the authority to say they’re the real quote for it). +3. Some higher authority (eg EC, Stakers) decides that should be the quote source for negotiable brand so it registers it under the real brand identity in the registry (with a new instance of a scaledPriceAuthority ). + +In practice we do these all in one core proposal. And each vault manager is started with a limit on minting to the EC has another way to gate transactions. + ## Usage See the [Smart Wallet integration test](/packages/inter-protocol/test/smartWallet/test-oracle-integration.js) for how it's used. diff --git a/packages/inter-protocol/src/proposals/addAssetToVault.js b/packages/inter-protocol/src/proposals/addAssetToVault.js index fce52bb9fde..d2b41dac909 100644 --- a/packages/inter-protocol/src/proposals/addAssetToVault.js +++ b/packages/inter-protocol/src/proposals/addAssetToVault.js @@ -12,8 +12,11 @@ import { Stable } from '@agoric/internal/src/tokens.js'; import { TimeMath } from '@agoric/time/src/timeMath.js'; import { makePromiseKit } from '@endo/promise-kit'; -import { instanceNameFor } from './price-feed-proposal.js'; -import { reserveThenGetNames } from './utils.js'; +import { + oracleBrandFeedName, + reserveThenGetNames, + scaledPriceFeedName, +} from './utils.js'; export * from './startPSM.js'; @@ -135,6 +138,7 @@ export const registerScaledPriceAuthority = async ( priceAuthorityAdmin, priceAuthority, }, + instance: { produce: produceInstance }, }, { options: { interchainAssetOptions } }, ) => { @@ -211,9 +215,11 @@ export const registerScaledPriceAuthority = async ( }), ); + const label = scaledPriceFeedName(issuerName); + const spaKit = await E(startUpgradable)({ installation: scaledPriceAuthority, - label: `scaledPriceAuthority-${issuerName}`, + label, terms, }); @@ -224,6 +230,11 @@ export const registerScaledPriceAuthority = async ( stableBrand, true, // force ); + + // 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 + produceInstance[label].resolve(spaKit.instance); }; // wait a short while after end to allow things to settle @@ -340,10 +351,12 @@ export const addAssetToVault = async ( [issuerName], ); - const oracleInstanceName = instanceNameFor(oracleBrand, 'USD'); // don't add the collateral offering to vaultFactory until its price feed is available // eslint-disable-next-line no-restricted-syntax -- allow this computed property - await consumeInstance[oracleInstanceName]; + await consumeInstance[oracleBrandFeedName(oracleBrand, 'USD')]; + // await also the negotiable brand + // eslint-disable-next-line no-restricted-syntax -- allow this computed property + await consumeInstance[scaledPriceFeedName(issuerName)]; const auctioneerCreator = E.get(auctioneerKit).creatorFacet; const schedules = await E(auctioneerCreator).getSchedule(); @@ -420,8 +433,8 @@ export const getManifestForAddAssetToVault = ( priceAuthorityAdmin: true, priceAuthority: true, }, - produce: { - scaledPriceAuthorityKits: true, + instance: { + produce: true, }, installation: { consume: { scaledPriceAuthority: true }, diff --git a/packages/inter-protocol/src/proposals/price-feed-proposal.js b/packages/inter-protocol/src/proposals/price-feed-proposal.js index 28daa78fa33..b0a55b67470 100644 --- a/packages/inter-protocol/src/proposals/price-feed-proposal.js +++ b/packages/inter-protocol/src/proposals/price-feed-proposal.js @@ -7,7 +7,14 @@ import { import { E } from '@endo/far'; import { unitAmount } from '@agoric/zoe/src/contractSupport/priceQuote.js'; -import { reserveThenDeposit, reserveThenGetNames } from './utils.js'; +import { + oracleBrandFeedName, + reserveThenDeposit, + reserveThenGetNames, +} from './utils.js'; + +// backwards compatibility +export { oracleBrandFeedName as instanceNameFor }; const trace = makeTracer('RunPriceFeed', true); @@ -18,9 +25,6 @@ const sanitizePathSegment = name => { return candidate; }; -export const instanceNameFor = (inBrandName, outBrandName) => - `${inBrandName}-${outBrandName} price feed`; - /** * @typedef {{ * brandIn?: ERef | undefined>; @@ -334,7 +338,7 @@ export const startPriceFeeds = async ( { options: { priceFeedOptions: { - AGORIC_INSTANCE_NAME: instanceNameFor(inBrandName, outBrandName), + AGORIC_INSTANCE_NAME: oracleBrandFeedName(inBrandName, outBrandName), contractTerms: { minSubmissionCount: 2, minSubmissionValue: 1, diff --git a/packages/inter-protocol/src/proposals/utils.js b/packages/inter-protocol/src/proposals/utils.js index 444544d0c8c..e073c0ea5e8 100644 --- a/packages/inter-protocol/src/proposals/utils.js +++ b/packages/inter-protocol/src/proposals/utils.js @@ -156,3 +156,9 @@ export const makeInstallCache = async ( return { wrapInstall, saveCache }; }; + +export const oracleBrandFeedName = (inBrandName, outBrandName) => + `${inBrandName}-${outBrandName} price feed`; + +export const scaledPriceFeedName = issuerName => + `scaledPriceAuthority-${issuerName}`; diff --git a/packages/inter-protocol/test/smartWallet/contexts.js b/packages/inter-protocol/test/smartWallet/contexts.js index bda0f17aa76..91cf8f017b0 100644 --- a/packages/inter-protocol/test/smartWallet/contexts.js +++ b/packages/inter-protocol/test/smartWallet/contexts.js @@ -10,10 +10,8 @@ import { import { makeHeapZone } from '@agoric/zone'; import { E } from '@endo/far'; import path from 'path'; -import { - createPriceFeed, - instanceNameFor, -} from '../../src/proposals/price-feed-proposal.js'; +import { oracleBrandFeedName } from '../../src/proposals/utils.js'; +import { createPriceFeed } from '../../src/proposals/price-feed-proposal.js'; import { withAmountUtils } from '../supports.js'; // referenced by TS @@ -187,7 +185,10 @@ export const makeDefaultTestContext = async (t, makeSpace) => { { options: { priceFeedOptions: { - AGORIC_INSTANCE_NAME: instanceNameFor(inBrandName, outBrandName), + AGORIC_INSTANCE_NAME: oracleBrandFeedName( + inBrandName, + outBrandName, + ), contractTerms: { minSubmissionCount: 2, minSubmissionValue: 1, diff --git a/packages/inter-protocol/test/smartWallet/test-oracle-integration.js b/packages/inter-protocol/test/smartWallet/test-oracle-integration.js index d131909378d..a11846e0c86 100644 --- a/packages/inter-protocol/test/smartWallet/test-oracle-integration.js +++ b/packages/inter-protocol/test/smartWallet/test-oracle-integration.js @@ -10,9 +10,9 @@ import { coalesceUpdates } from '@agoric/smart-wallet/src/utils.js'; import { TimeMath } from '@agoric/time'; import buildManualTimer from '@agoric/zoe/tools/manualTimer.js'; import { E } from '@endo/far'; +import { oracleBrandFeedName } from '../../src/proposals/utils.js'; import { INVITATION_MAKERS_DESC as EC_INVITATION_MAKERS_DESC } from '../../src/econCommitteeCharter.js'; import { INVITATION_MAKERS_DESC as ORACLE_INVITATION_MAKERS_DESC } from '../../src/price/fluxAggregatorKit.js'; -import { instanceNameFor } from '../../src/proposals/price-feed-proposal.js'; import { headValue } from '../supports.js'; import { buildRootObject } from './boot-psm.js'; import { @@ -126,7 +126,7 @@ const setupFeedWithWallets = async (t, oracleAddresses) => { */ const governedPriceAggregator = await E(agoricNames).lookup( 'instance', - instanceNameFor('ATOM', 'USD'), + oracleBrandFeedName('ATOM', 'USD'), ); return { oracleWallets, governedPriceAggregator }; @@ -476,7 +476,7 @@ test.serial('govern oracles list', async t => { const feed = await E(agoricNames).lookup( 'instance', - instanceNameFor('ATOM', 'USD'), + oracleBrandFeedName('ATOM', 'USD'), ); t.assert(feed);