-
Notifications
You must be signed in to change notification settings - Fork 226
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
9584 upgrade price feeds #10074
Changes from all commits
fd91f78
03cd585
0b047f0
841cf20
95b2b1d
5405d47
eb941d4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
nodeLinker: node-modules |
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. | ||
|
||
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). |
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 => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can you add a comment along the lines of
|
||
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; | ||
}; |
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 => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. please add a "kludged part of marshal" note on this one too. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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), | ||
), | ||
); | ||
}; |
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 |
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 => { | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this check that cardinality is 2? we talked about that. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Turns out that is happening in |
||||||||||
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); | ||||||||||
}; | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. consider using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
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'); | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); |
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 |
There was a problem hiding this comment.
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