Skip to content

Commit

Permalink
Merge pull request #7892 from Agoric/ta/multiple-collats
Browse files Browse the repository at this point in the history
btest adding collateral
  • Loading branch information
turadg authored and mhofman committed Aug 7, 2023
2 parents 60c841c + e8cc789 commit 13cfd29
Show file tree
Hide file tree
Showing 16 changed files with 423 additions and 233 deletions.
3 changes: 2 additions & 1 deletion packages/agoric-cli/src/commands/oracle.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Nat } from '@endo/nat';
import { Command } from 'commander';
import * as cp from 'child_process';
import { inspect } from 'util';
import { instanceNameFor } from '@agoric/inter-protocol/src/proposals/price-feed-proposal.js';
import { normalizeAddressWithOptions } from '../lib/chain.js';
import { getNetworkConfig, makeRpcUtils, storageHelper } from '../lib/rpc.js';
import {
Expand Down Expand Up @@ -75,7 +76,7 @@ export const makeOracleCommand = (logger, io = {}) => {
const utils = await makeRpcUtils({ fetch });

const lookupPriceAggregatorInstance = ([brandIn, brandOut]) => {
const name = `${brandIn}-${brandOut} price feed`;
const name = instanceNameFor(brandIn, brandOut);
const instance = utils.agoricNames.instance[name];
if (!instance) {
logger.debug('known instances:', utils.agoricNames.instance);
Expand Down
1 change: 1 addition & 0 deletions packages/inter-protocol/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"scripts": {
"build": "yarn build:bundles",
"build:bundles": "node ./scripts/build-bundles.js",
"build:add-STARS-proposal": "agoric run scripts/add-STARS.js",
"test": "ava",
"test:c8": "c8 $C8_OPTIONS ava --config=ava-nesm.config.js",
"test:xs": "exit 0",
Expand Down
43 changes: 43 additions & 0 deletions packages/inter-protocol/scripts/add-STARS.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { makeHelpers } from '@agoric/deploy-script-support';
import { defaultProposalBuilder as vaultProposalBuilder } from './add-collateral-core.js';
import { defaultProposalBuilder as oraclesProposalBuilder } from './price-feed-core.js';

/** @type {import('@agoric/deploy-script-support/src/externalTypes.js').ProposalBuilder} */
export const starsVaultProposalBuilder = async powers => {
return vaultProposalBuilder(powers, {
interchainAssetOptions: {
// Values for the Stargaze token on Osmosis
denom:
'ibc/987C17B11ABC2B20019178ACE62929FE9840202CE79498E29FE8E5CB02B7C0A4',
decimalPlaces: 6,
keyword: 'STARS',
oracleBrand: 'STARS',
proposedName: 'STARS',
},
});
};

/** @type {import('@agoric/deploy-script-support/src/externalTypes.js').ProposalBuilder} */
export const starsOraclesProposalBuilder = async powers => {
return oraclesProposalBuilder(powers, {
AGORIC_INSTANCE_NAME: `STARS-USD price feed`,
IN_BRAND_LOOKUP: ['agoricNames', 'oracleBrand', 'STARS'],
IN_BRAND_DECIMALS: 6,
OUT_BRAND_LOOKUP: ['agoricNames', 'oracleBrand', 'USD'],
OUT_BRAND_DECIMALS: 4,
oracleAddresses: [
// copied from decentral-main-vaults-config.json
'agoric1krunjcqfrf7la48zrvdfeeqtls5r00ep68mzkr',
'agoric19uscwxdac6cf6z7d5e26e0jm0lgwstc47cpll8',
'agoric144rrhh4m09mh7aaffhm6xy223ym76gve2x7y78',
'agoric19d6gnr9fyp6hev4tlrg87zjrzsd5gzr5qlfq2p',
'agoric1n4fcxsnkxe4gj6e24naec99hzmc4pjfdccy5nj',
],
});
};

export default async (homeP, endowments) => {
const { writeCoreProposal } = await makeHelpers(homeP, endowments);
await writeCoreProposal('add-STARS', starsVaultProposalBuilder);
await writeCoreProposal('add-STARS-oracles', starsOraclesProposalBuilder);
};
16 changes: 14 additions & 2 deletions packages/inter-protocol/src/proposals/addAssetToVault.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
// @jessie-check

import { AmountMath, AssetKind } from '@agoric/ertp';
import { makeRatio } from '@agoric/zoe/src/contractSupport/index.js';
import { deeplyFulfilledObject } from '@agoric/internal';
import { Stable } from '@agoric/vats/src/tokens.js';
import { E } from '@endo/far';
import { makeRatio } from '@agoric/zoe/src/contractSupport/index.js';
import { parseRatio } from '@agoric/zoe/src/contractSupport/ratio.js';
import { E } from '@endo/far';
import { instanceNameFor } from './price-feed-proposal.js';
import { reserveThenGetNames } from './utils.js';

export * from './startPSM.js';
Expand Down Expand Up @@ -219,6 +220,7 @@ export const addAssetToVault = async (
brand: {
consume: { [Stable.symbol]: stableP },
},
instance: { consume: consumeInstance },
},
{
options: {
Expand All @@ -239,6 +241,11 @@ export const addAssetToVault = async (
[keyword],
);

const oracleInstanceName = instanceNameFor(oracleBrand, 'USD');
// don't add the collateral offering to vaultFactory until its price feed is available
// eslint-disable-next-line no-restricted-syntax -- allow this computed property
await consumeInstance[oracleInstanceName];

const stable = await stableP;
const vaultFactoryCreator = E.get(vaultFactoryKit).creatorFacet;
await E(vaultFactoryCreator).addVaultType(interchainIssuer, oracleBrand, {
Expand Down Expand Up @@ -321,6 +328,11 @@ export const getManifestForAddAssetToVault = (
brand: {
consume: { [Stable.symbol]: true },
},
instance: {
// allow any instance because the AGORIC_INSTANCE_NAME of
// priceFeedOptions cannot be known statically.
consume: true,
},
},
},
installations: {
Expand Down
43 changes: 29 additions & 14 deletions packages/inter-protocol/src/proposals/price-feed-proposal.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
// @ts-nocheck -- lots of type errors. low prio b/c proposals are like scripts
import { E } from '@endo/far';
import { makeTracer } from '@agoric/internal';
import {
makeStorageNodeChild,
assertPathSegment,
makeStorageNodeChild,
} from '@agoric/internal/src/lib-chainStorage.js';
import { makeTracer } from '@agoric/internal';
import { E } from '@endo/far';

import { unitAmount } from '@agoric/zoe/src/contractSupport/priceQuote.js';
import { reserveThenDeposit, reserveThenGetNames } from './utils.js';
Expand All @@ -18,6 +18,9 @@ const sanitizePathSegment = name => {
return candidate;
};

export const instanceNameFor = (inBrandName, outBrandName) =>
`${inBrandName}-${outBrandName} price feed`;

/**
* @typedef {{
* brandIn?: ERef<Brand<'nat'> | undefined>,
Expand Down Expand Up @@ -95,6 +98,7 @@ export const createPriceFeed = async (
priceAuthorityAdmin,
startGovernedUpgradable,
},
instance: { produce: produceInstance },
},
{
options: {
Expand Down Expand Up @@ -160,11 +164,27 @@ export const createPriceFeed = async (
installation: priceAggregator,
});

E(E(agoricNamesAdmin).lookupAdmin('instance'))
// Publish price feed in home.priceAuthority.
const forceReplace = true;
// Make sure this PA is registered before sharing the instance in agoricNames,
// which allows contracts that depend on the registry value to wait for it and
// prevent a race.
await E(priceAuthorityAdmin).registerPriceAuthority(
E(faKit.publicFacet).getPriceAuthority(),
brandIn,
brandOut,
forceReplace,
);

await E(E(agoricNamesAdmin).lookupAdmin('instance'))
.update(AGORIC_INSTANCE_NAME, faKit.instance)
.catch(err =>
console.error(`🚨 failed to update ${AGORIC_INSTANCE_NAME}`, err),
);
// being after the above awaits means that when this resolves, the consumer
// gets notified that the authority is in the registry and its instance is in
// agoricNames.
produceInstance[AGORIC_INSTANCE_NAME].resolve(faKit.instance);

E(E.get(econCharterKit).creatorFacet).addInstance(
faKit.instance,
Expand All @@ -173,15 +193,6 @@ export const createPriceFeed = async (
);
trace('registered', AGORIC_INSTANCE_NAME, faKit.instance);

// Publish price feed in home.priceAuthority.
const forceReplace = true;
void E(priceAuthorityAdmin).registerPriceAuthority(
E(faKit.publicFacet).getPriceAuthority(),
brandIn,
brandOut,
forceReplace,
);

/**
* Initialize a new oracle and send an invitation to administer it.
*
Expand Down Expand Up @@ -231,6 +242,9 @@ export const getManifestForPriceFeed = async (
priceAuthorityAdmin: t,
startGovernedUpgradable: t,
},
instance: {
produce: t,
},
},
[ensureOracleBrands.name]: {
namedVat: {
Expand All @@ -242,6 +256,7 @@ export const getManifestForPriceFeed = async (
},
},
installations: {
// ??? will every eval of price-feed-proposal install priceAggregator ?
priceAggregator: restoreRef(priceFeedOptions.priceAggregatorRef),
},
options: {
Expand Down Expand Up @@ -302,7 +317,7 @@ export const startPriceFeeds = async (
{
options: {
priceFeedOptions: {
AGORIC_INSTANCE_NAME: `${inBrandName}-${outBrandName} price feed`,
AGORIC_INSTANCE_NAME: instanceNameFor(inBrandName, outBrandName),
contractTerms: {
minSubmissionCount: 2,
minSubmissionValue: 1,
Expand Down
2 changes: 2 additions & 0 deletions packages/inter-protocol/src/vaultFactory/vaultDirector.js
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ const prepareVaultDirector = (
metrics: makeRecorderTopic('Vault Factory metrics', metricsKit),
});

/** @param {(vm: VaultManager) => void} fn */
const allManagersDo = fn => {
for (const managerIndex of collateralManagers.values()) {
const vm = vaultManagers.get(managerIndex).self;
Expand Down Expand Up @@ -424,6 +425,7 @@ const prepareVaultDirector = (

makeLiquidationWaker() {
return makeWaker('liquidationWaker', _timestamp => {
// XXX floating promise
allManagersDo(vm => vm.liquidateVaults(auctioneer));
});
},
Expand Down
22 changes: 15 additions & 7 deletions packages/inter-protocol/src/vaultFactory/vaultManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,7 @@ export const prepareVaultManagerKit = (
// throw. See https://github.com/Agoric/agoric-sdk/issues/4317
void observeNotifier(quoteNotifier, {
updateState(value) {
trace('vaultManager got new collateral quote', value);
trace('storing new quote', value.quoteAmount.value);
ephemera.storedCollateralQuote = value;
},
fail(reason) {
Expand Down Expand Up @@ -1036,7 +1036,7 @@ export const prepareVaultManagerKit = (
if (!storedCollateralQuote)
throw Fail`lockOraclePrices called before a collateral quote was available`;
trace(
`lockPrice`,
`lockOraclePrices`,
getAmountIn(storedCollateralQuote),
getAmountOut(storedCollateralQuote),
);
Expand All @@ -1046,7 +1046,7 @@ export const prepareVaultManagerKit = (
return storedCollateralQuote;
},
/**
* @param {AuctioneerPublicFacet} auctionPF
* @param {ERef<AuctioneerPublicFacet>} auctionPF
*/
async liquidateVaults(auctionPF) {
const { state, facets } = this;
Expand All @@ -1060,11 +1060,19 @@ export const prepareVaultManagerKit = (
} = state;
trace(collateralBrand, 'considering liquidation');

if (!lockedQuote) {
// By design, the first cycle of auction may call this before a quote is locked
// because the schedule is global at the vaultDirector level, and if a manager
// starts after the price lock time there's nothing to be done.
// NB: this message should not log repeatedly.
console.error(
'Skipping liquidation because no quote is locked yet (may happen with new manager)',
);
return;
}

const { prioritizedVaults } = collateralEphemera(collateralBrand);
assert(factoryPowers && prioritizedVaults && zcf);
lockedQuote ||
Fail`Must have locked a quote before liquidating vaults.`;
assert(lockedQuote); // redundant with previous line
prioritizedVaults || Fail`prioritizedVaults missing from ephemera`;

const liqMargin = self.getGovernedParams().getLiquidationMargin();

Expand Down
20 changes: 16 additions & 4 deletions packages/inter-protocol/test/smartWallet/contexts.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ import {
import { heapZone } from '@agoric/zone';
import { E } from '@endo/far';
import path from 'path';
import { createPriceFeed } from '../../src/proposals/price-feed-proposal.js';
import {
createPriceFeed,
instanceNameFor,
} from '../../src/proposals/price-feed-proposal.js';
import { withAmountUtils } from '../supports.js';

// referenced by TS
Expand Down Expand Up @@ -42,7 +45,8 @@ export const makeDefaultTestContext = async (t, makeSpace) => {

const bundleCache = await unsafeMakeBundleCache('bundles/');

const { consume, produce } = await makeSpace(log, bundleCache);
// @ts-expect-error xxx
const { consume, produce, instance } = await makeSpace(log, bundleCache);
const { agoricNames, zoe } = consume;

await produceDiagnostics({ produce });
Expand Down Expand Up @@ -155,12 +159,20 @@ export const makeDefaultTestContext = async (t, makeSpace) => {
const paInstallation = E(zoe).install(paBundle);
await E(installAdmin).update('priceAggregator', paInstallation);

const mockPriceAuthorityAdmin = /** @type {any} */ ({
registerPriceAuthority() {
// noop mock
},
});
produce.priceAuthorityAdmin.resolve(mockPriceAuthorityAdmin);

await createPriceFeed(
{ consume, produce },
// @ts-expect-error xxx
{ consume, produce, instance },
{
options: {
priceFeedOptions: {
AGORIC_INSTANCE_NAME: `${inBrandName}-${outBrandName} price feed`,
AGORIC_INSTANCE_NAME: instanceNameFor(inBrandName, outBrandName),
contractTerms: {
minSubmissionCount: 2,
minSubmissionValue: 1,
Expand Down
27 changes: 16 additions & 11 deletions packages/inter-protocol/test/smartWallet/test-oracle-integration.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,18 @@ import '@agoric/vats/src/core/types.js';
import { test as anyTest } from '@agoric/zoe/tools/prepare-test-env-ava.js';

import { NonNullish } from '@agoric/assert';
import { coalesceUpdates } from '@agoric/smart-wallet/src/utils.js';
import { AssetKind, makeIssuerKit } from '@agoric/ertp';
import { zip } from '@agoric/internal';
import { eventLoopIteration } from '@agoric/internal/src/testing-utils.js';
import buildManualTimer from '@agoric/zoe/tools/manualTimer.js';
import { coalesceUpdates } from '@agoric/smart-wallet/src/utils.js';
import { TimeMath } from '@agoric/time';
import buildManualTimer from '@agoric/zoe/tools/manualTimer.js';
import { E } from '@endo/far';
import { zip } from '@agoric/internal';
import { AssetKind, makeIssuerKit } from '@agoric/ertp';
import { buildRootObject } from './boot-psm.js';
import { INVITATION_MAKERS_DESC as EC_INVITATION_MAKERS_DESC } from '../../src/econCommitteeCharter.js';
import { INVITATION_MAKERS_DESC as ORACLE_INVITATION_MAKERS_DESC } from '../../src/price/fluxAggregatorKit.js';
import { instanceNameFor } from '../../src/proposals/price-feed-proposal.js';
import { headValue } from '../supports.js';
import { buildRootObject } from './boot-psm.js';
import {
currentPurseBalance,
importBootTestUtils,
Expand All @@ -22,9 +23,10 @@ import {
} from './contexts.js';

/**
* @type {import('ava').TestFn<Awaited<ReturnType<makeDefaultTestContext>>
* & {consume: import('@agoric/inter-protocol/src/proposals/econ-behaviors.js').EconomyBootstrapPowers['consume']}>
* }
* @typedef {Awaited<ReturnType<typeof makeDefaultTestContext>>
& { consume: import('@agoric/inter-protocol/src/proposals/econ-behaviors.js').EconomyBootstrapPowers['consume'] } } TestContext */
/**
* @type {import('ava').TestFn<TestContext>}
*/
const test = anyTest;

Expand Down Expand Up @@ -104,7 +106,7 @@ test.before(async t => {
});

/**
* @param {import('ava').ExecutionContext<*>} t
* @param {import('ava').ExecutionContext<TestContext>} t
* @param {string[]} oracleAddresses
*/
const setupFeedWithWallets = async (t, oracleAddresses) => {
Expand All @@ -121,7 +123,7 @@ const setupFeedWithWallets = async (t, oracleAddresses) => {
/** @type {import('@agoric/zoe/src/zoeService/utils.js').Instance<import('@agoric/inter-protocol/src/price/fluxAggregatorContract.js').prepare>} */
const governedPriceAggregator = await E(agoricNames).lookup(
'instance',
'ATOM-USD price feed',
instanceNameFor('ATOM', 'USD'),
);

return { oracleWallets, governedPriceAggregator };
Expand Down Expand Up @@ -463,7 +465,10 @@ test.serial('govern oracles list', async t => {
t.is(usedInvitations.get('acceptVoterOID')?.value[0].description, 'Voter0');
}

const feed = await E(agoricNames).lookup('instance', 'ATOM-USD price feed');
const feed = await E(agoricNames).lookup(
'instance',
instanceNameFor('ATOM', 'USD'),
);
t.assert(feed);

// Call for a vote to addOracles ////////////////////////////////
Expand Down
Loading

0 comments on commit 13cfd29

Please sign in to comment.