-
Notifications
You must be signed in to change notification settings - Fork 221
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
closes: #9584 closes: #9928 refs: #9827 refs: #9748 refs: #9382 closes: #10031 ## Description We added upgrading the scaledPriceAuthority to the steps in upgrading vaults, auctions, and priceFeeds, and didn't notice that it broke things. The problem turned out to be that the "priceAuthority" object registered with the priceFeedRegistry was an ephemeral object that was not upgraded. This fixes that by re-registering the new priceAuthority. Then, to simplify the process of cleaning up the uncollected cycles reported in #9483, we switched to replacing the scaledPriceAuthorities rather than upgrading them. We also realized that we would need different coreEvals in different environments, since the Oracle addresses and particular assets vary for each (test and mainNet) chain environment. #9748 addressed some of the issues in the original coreEval. #9999 showed what was needed for upgrading priceFeeds, which was completed by #9827. #10021 added some details on replacing scaledPriceAuthorities. ### Security Considerations N/A ### Scaling Considerations Addresses one of our biggest scaling issues. ### Documentation Considerations N/A ### Testing Considerations Thorough testing in a3p, and our testnets. #9886 discusses some testing to ensure Oracles will work with the upgrade. ### Upgrade Considerations See above
- Loading branch information
Showing
34 changed files
with
3,875 additions
and
81 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
nodeLinker: node-modules |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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). |
23 changes: 23 additions & 0 deletions
23
a3p-integration/proposals/f:replace-price-feeds/agd-tools.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 => { | ||
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
96
a3p-integration/proposals/f:replace-price-feeds/agoric-tools.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 => { | ||
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), | ||
), | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
28
a3p-integration/proposals/f:replace-price-feeds/package.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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]" | ||
} |
155 changes: 155 additions & 0 deletions
155
a3p-integration/proposals/f:replace-price-feeds/priceFeedUpdate.test.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 => { | ||
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); | ||
}; | ||
|
||
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'); | ||
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); | ||
}); |
41 changes: 41 additions & 0 deletions
41
a3p-integration/proposals/f:replace-price-feeds/resetChargingPeriod.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Oops, something went wrong.