Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

9584 upgrade price feeds #10074

Merged
merged 7 commits into from
Oct 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
nodeLinker: node-modules
10 changes: 10 additions & 0 deletions a3p-integration/proposals/f:replace-price-feeds/README.md
Original file line number Diff line number Diff line change
@@ -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.
Comment on lines +1 to +5
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure you meant this all to be a heading


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).
23 changes: 23 additions & 0 deletions a3p-integration/proposals/f:replace-price-feeds/agd-tools.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { agd } from '@agoric/synthetic-chain';

export const BID_OFFER_ID = 'bid-vaultUpgrade-test3';

/** @param {string} path */
export const queryVstorage = path =>
agd.query('vstorage', 'data', '--output', 'json', path);

export const getOracleInstance = async price => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you add a comment along the lines of

  // XXX kludge of some of marshal. see https://github.com/Agoric/agoric-3-proposals/issues/176

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;
};
96 changes: 96 additions & 0 deletions a3p-integration/proposals/f:replace-price-feeds/agoric-tools.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import assert from 'node:assert';
import { agops, agoric, executeOffer } from '@agoric/synthetic-chain';

export const generateVaultDirectorParamChange = async (
previousOfferId,
voteDur,
params,
paramsPath,
) => {
const voteDurSec = BigInt(voteDur);
const toSec = ms => BigInt(Math.round(ms / 1000));

const id = `propose-${Date.now()}`;
const deadline = toSec(Date.now()) + voteDurSec;

const zip = (xs, ys) => xs.map((x, i) => [x, ys[i]]);
// KLUDGE: partial deconstruction of smallCaps values
const fromSmallCapsEntries = txt => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please add a "kludged part of marshal" note on this one too.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

const { body, slots } = JSON.parse(txt);
const theEntries = zip(JSON.parse(body.slice(1)), slots).map(
([[name, ref], boardID]) => {
const iface = ref.replace(/^\$\d+\./, '');
return [name, { iface, boardID }];
},
);
return Object.fromEntries(theEntries);
};

const slots = []; // XXX global mutable state
const smallCaps = {
Nat: n => `+${n}`,
// XXX mutates obj
ref: obj => {
if (obj.ix) return obj.ix;
const ix = slots.length;
slots.push(obj.boardID);
obj.ix = `$${ix}.Alleged: ${obj.iface}`;
return obj.ix;
},
};

await null;
const instance = fromSmallCapsEntries(
await agoric.follow('-lF', ':published.agoricNames.instance', '-o', 'text'),
);
assert(instance.VaultFactory);

const body = {
method: 'executeOffer',
offer: {
id,
invitationSpec: {
invitationMakerName: 'VoteOnParamChange',
previousOffer: previousOfferId,
source: 'continuing',
},
offerArgs: {
deadline: smallCaps.Nat(deadline),
instance: smallCaps.ref(instance.VaultFactory),
params,
path: paramsPath,
},
proposal: {},
},
};

const capData = { body: `#${JSON.stringify(body)}`, slots };
return JSON.stringify(capData);
};

export const proposeVaultDirectorParamChange = async (
address,
params,
path,
) => {
const charterAcceptOfferId = await agops.ec(
'find-continuing-id',
'--for',
`${'charter\\ member\\ invitation'}`,
'--from',
address,
);

return executeOffer(
address,
generateVaultDirectorParamChange(charterAcceptOfferId, 30, params, path),
);
};

export const voteForNewParams = (accounts, position) => {
return Promise.all(
accounts.map(account =>
agops.ec('vote', '--forPosition', position, '--send-from', account),
),
);
};
15 changes: 15 additions & 0 deletions a3p-integration/proposals/f:replace-price-feeds/eval.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/bin/bash

# Exit when any command fails
set -uxeo pipefail

# The upgrade-vaults proposal needs to know the existing vaultDirector
# parameters in order to cleanly upgrade the contract. The governance notifier
# it relies on doesn't give the most recent value if there were no updates to
# the parameters, so we'll do a governance action to reset them to their current
# values so the notifier will work.

./resetChargingPeriod.js

cp /usr/src/upgrade-test-scripts/eval_submission.js .
./eval_submission.js
28 changes: 28 additions & 0 deletions a3p-integration/proposals/f:replace-price-feeds/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"agoricProposal": {
"source": "subdir",
"sdk-generate": [
"inter-protocol/updatePriceFeeds.js submission A3P_INTEGRATION",
"vats/add-auction.js",
"vats/upgradeVaults.js"
],
"type": "/agoric.swingset.CoreEvalProposal"
},
"type": "module",
"license": "Apache-2.0",
"dependencies": {
"@agoric/synthetic-chain": "^0.3.0",
"ava": "^5.3.1"
},
"ava": {
"concurrency": 1,
"timeout": "2m",
"files": [
"!submission"
]
},
"scripts": {
"agops": "yarn --cwd /usr/src/agoric-sdk/ --silent agops"
},
"packageManager": "[email protected]"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import test from 'ava';

import {
agops,
ATOM_DENOM,
bankSend,
createBid,
generateOracleMap,
getDetailsMatchingVats,
getInstanceBoardId,
getISTBalance,
getLiveOffers,
getPriceQuote,
getVaultPrices,
getVatDetails,
openVault,
pushPrices,
registerOraclesForBrand,
USER1ADDR,
} from '@agoric/synthetic-chain';

import { BID_OFFER_ID } from './agd-tools.js';

export const checkForOracle = async (t, name) => {
const instanceName = `${name}-USD price feed`;
const instance = await getInstanceBoardId(instanceName);
t.truthy(instance);
};

const checkPriceFeedVatsUpdated = async t => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this check that cardinality is 2? we talked about that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Turns out that is happening in countPriceFeedVats()

const atomDetails = await getVatDetails('ATOM-USD_price_feed');
// both the original and the new ATOM vault are incarnation 0
t.is(atomDetails.incarnation, 0);
const stAtomDetails = await getVatDetails('stATOM');
t.is(stAtomDetails.incarnation, 0);
await checkForOracle(t, 'ATOM');
await checkForOracle(t, 'stATOM');
};

console.log('adding oracle for each brand');
const oraclesByBrand = generateOracleMap('f-priceFeeds', ['ATOM', 'stATOM']);
await registerOraclesForBrand('ATOM', oraclesByBrand);
await registerOraclesForBrand('stATOM', oraclesByBrand);

let roundId = 1;

const tryPushPrices = async t => {
// There are no old prices for the other currencies.
// const atomOutPre = await getPriceQuote('ATOM');
// t.is(atomOutPre, '+12010000');
// const stAtomOutPre = await getPriceQuote('stATOM');
// t.is(stAtomOutPre, '+12010000');

t.log('pushing new prices');
await pushPrices(13.4, 'ATOM', oraclesByBrand, roundId);
await pushPrices(13.7, 'stATOM', oraclesByBrand, roundId);
roundId += 1;

t.log('awaiting new quotes');
const atomOut = await getPriceQuote('ATOM');
t.is(atomOut, '+13400000');
const stAtomOut = await getPriceQuote('stATOM');
t.is(stAtomOut, '+13700000');
};

const createNewBid = async t => {
await createBid('20', USER1ADDR, BID_OFFER_ID);
const liveOffer = await getLiveOffers(USER1ADDR);
t.true(liveOffer[0].includes(BID_OFFER_ID));
};

const openMarginalVault = async t => {
let user1IST = await getISTBalance(USER1ADDR);
await bankSend(USER1ADDR, `20000000${ATOM_DENOM}`);
const currentVaults = await agops.vaults('list', '--from', USER1ADDR);

t.log('opening a vault');
await openVault(USER1ADDR, 5, 10);
user1IST += 5;
const istBalanceAfterVaultOpen = await getISTBalance(USER1ADDR);
t.is(istBalanceAfterVaultOpen, user1IST);

const activeVaultsAfter = await agops.vaults('list', '--from', USER1ADDR);
t.log(currentVaults, activeVaultsAfter);
t.true(
activeVaultsAfter.length > currentVaults.length,
`vaults count should increase, ${activeVaultsAfter.length}, ${currentVaults.length}`,
);
};

const triggerAuction = async t => {
await pushPrices(5.2, 'ATOM', oraclesByBrand, roundId);

const atomOut = await getPriceQuote('ATOM');
t.is(atomOut, '+5200000');
};

const checkNewAuctionVat = async t => {
const details = await getDetailsMatchingVats('auctioneer');
// This query matches both the auction and its governor, so double the count
t.is(Object.keys(details).length, 3 * 2);
};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

consider using t.like to check details

Copy link
Contributor Author

@Chris-Hibbert Chris-Hibbert Oct 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

details only has vatID, incarnation, vatName, and bundleID, so there's not much value. counting instances is all I can do.

But the number does need to be updated, This is the third instance.


const countPriceFeedVats = async t => {
// price_feed and governor, old and new for two tokens
const priceFeedDetails = await getDetailsMatchingVats('price_feed');
t.is(Object.keys(priceFeedDetails).length, 8);

// Two old SPAs, and two new ones
const details = await getDetailsMatchingVats('scaledPriceAuthority');
t.is(Object.keys(details).length, 4);

// ATOM vat name is something like zcf-DEADBEEF-ATOM_USD_price_feed
// initial '-' distinguishes this from stAOM
const atomDetails = await getDetailsMatchingVats('-ATOM-USD_price_feed');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const atomDetails = await getDetailsMatchingVats('-ATOM-USD_price_feed');
// ATOM vat name is something like zcf-deadbeaf-ATOM_USD_price_feed
// initial '-' distinguishes this from stAOM
const atomDetails = await getDetailsMatchingVats('-ATOM-USD_price_feed');

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

t.is(Object.keys(atomDetails).length, 4);

const stAtomDetails = await getVatDetails('stATOM');
t.is(Object.keys(stAtomDetails).length, 4);
await Promise.all([checkForOracle(t, 'ATOM'), checkForOracle(t, 'stATOM')]);
};

const verifyVaultPriceUpdate = async t => {
const ATOMManagerIndex = 0;
const quote = await getVaultPrices(ATOMManagerIndex);
t.true(quote.value[0].amountIn.brand.includes(' ATOM '));
t.is(quote.value[0].amountOut.value, '+5200000');
};

// test.serial() isn't guaranteed to run tests in order, so we run the intended tests here
test('liquidation post upgrade', async t => {
t.log('starting upgrade vaults test');
await checkPriceFeedVatsUpdated(t);

t.log('starting pushPrices');
await tryPushPrices(t);

t.log('create a new Bid for the auction');
await createNewBid(t);

t.log('open a marginal vault');
await openMarginalVault(t);

t.log('trigger Auction');
await triggerAuction(t);

t.log('check new auction');
await checkNewAuctionVat(t);

t.log('count vats');
await countPriceFeedVats(t);

t.log('verify Vault priceUpdate');
await verifyVaultPriceUpdate(t);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#!/usr/bin/env node

/* global setTimeout */

import {
getQuoteBody,
GOV1ADDR,
GOV2ADDR,
GOV3ADDR,
} from '@agoric/synthetic-chain';
import {
proposeVaultDirectorParamChange,
voteForNewParams,
} from './agoric-tools.js';

const GOV_ADDRESSES = [GOV1ADDR, GOV2ADDR, GOV3ADDR];

const readChargingPeriod = async () => {
const governanceBody = await getQuoteBody(
'published.vaultFactory.governance',
);
const period =
governanceBody.current.ChargingPeriod.value.match(/\+?(\d+)/)[1];
return `+${period}`;
};

const setChargingPeriod = async period => {
const params = {
ChargingPeriod: period,
};

const path = { paramPath: { key: 'governedParams' } };

await proposeVaultDirectorParamChange(GOV1ADDR, params, path);
await voteForNewParams(GOV_ADDRESSES, 0);

await new Promise(r => setTimeout(r, 65000));
};

const period = await readChargingPeriod();
await setChargingPeriod(period);
6 changes: 6 additions & 0 deletions a3p-integration/proposals/f:replace-price-feeds/test.sh
Original file line number Diff line number Diff line change
@@ -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
Loading
Loading