Skip to content

Commit

Permalink
Add a coreEval for vaults auctions and test in a3p (#9911)
Browse files Browse the repository at this point in the history
closes: #9887

## Description

Create a coreEval that replaces the Auctions contract, and upgrades VaultFactory.

### Security Considerations

No security-relevant changes.

### Scaling Considerations

Doesn't make things better or worse.

### Documentation Considerations

N/A

### Testing Considerations

Includes a test in a3p-integration that shows that there's a new auction, that vaultFactory upgrades, that new bids submitted go to the new auction, and that the upgraded vaults respond to price updates.

### Upgrade Considerations

Upgrade is tested.
  • Loading branch information
mergify[bot] authored Aug 19, 2024
2 parents c4520c5 + de3b258 commit aaa0def
Show file tree
Hide file tree
Showing 32 changed files with 3,107 additions and 674 deletions.
30 changes: 0 additions & 30 deletions a3p-integration/proposals/a:upgrade-next/upgradeVaults.js

This file was deleted.

6 changes: 6 additions & 0 deletions a3p-integration/proposals/a:vaults-auctions/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# CoreEvalProposal to upgrade vaults and auctions

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).
203 changes: 203 additions & 0 deletions a3p-integration/proposals/a:vaults-auctions/agd-tools.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
import {
agd,
agops,
agopsLocation,
CHAINID,
executeCommand,
executeOffer,
GOV1ADDR,
GOV2ADDR,
GOV3ADDR,
VALIDATORADDR,
waitForBlock,
} from '@agoric/synthetic-chain';

const ORACLE_ADDRESSES = [GOV1ADDR, GOV2ADDR, GOV3ADDR];

const queryVstorage = path =>
agd.query('vstorage', 'data', '--output', 'json', path);

// XXX use endo/marshal?
const getQuoteBody = async path => {
const queryOut = await queryVstorage(path);

const body = JSON.parse(JSON.parse(queryOut.value).values[0]);
return JSON.parse(body.body.substring(1));
};

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;
};

export const checkForOracle = async (t, name) => {
const instance = await getOracleInstance(name);
t.truthy(instance);
};

export const registerOraclesForBrand = async (brandIn, oraclesByBrand) => {
await null;
const promiseArray = [];

const oraclesWithID = oraclesByBrand.get(brandIn);
for (const oracle of oraclesWithID) {
const { address, offerId } = oracle;
promiseArray.push(
executeOffer(
address,
agops.oracle('accept', '--offerId', offerId, `--pair ${brandIn}.USD`),
),
);
}

return Promise.all(promiseArray);
};

/**
* Generate a consistent map of oracleIDs for a brand that can be used to
* register oracles or to push prices. The baseID changes each time new
* invitations are sent/accepted, and need to be maintained as constants in
* scripts that use the oracles. Each oracleAddress and brand needs a unique
* offerId, so we create recoverable IDs using the brandName and oracle id,
* mixed with the upgrade at which the invitations were accepted.
*
* @param {string} baseId
* @param {string} brandName
*/
const addOraclesForBrand = (baseId, brandName) => {
const oraclesWithID = [];
for (let i = 0; i < ORACLE_ADDRESSES.length; i += 1) {
const oracleAddress = ORACLE_ADDRESSES[i];
const offerId = `${brandName}.${baseId}.${i}`;
oraclesWithID.push({ address: oracleAddress, offerId });
}
return oraclesWithID;
};

export const addPreexistingOracles = async (brandIn, oraclesByBrand) => {
await null;

const oraclesWithID = [];
for (let i = 0; i < ORACLE_ADDRESSES.length; i += 1) {
const oracleAddress = ORACLE_ADDRESSES[i];

const path = `published.wallet.${oracleAddress}.current`;
const wallet = await getQuoteBody(path);
const idToInvitation = wallet.offerToUsedInvitation.find(([k]) => {
return !isNaN(k[0]);
});
if (idToInvitation) {
oraclesWithID.push({
address: oracleAddress,
offerId: idToInvitation[0],
});
}
}

oraclesByBrand.set(brandIn, oraclesWithID);
};

/**
* Generate a consistent map of oracleIDs and brands that can be used to
* register oracles or to push prices. The baseID changes each time new
* invitations are sent/accepted, and need to be maintained as constants in
* scripts that use these records to push prices.
*
* @param {string} baseId
* @param {string[]} brandNames
*/
export const generateOracleMap = (baseId, brandNames) => {
const oraclesByBrand = new Map();
for (const brandName of brandNames) {
const oraclesWithID = addOraclesForBrand(baseId, brandName);
oraclesByBrand.set(brandName, oraclesWithID);
}
return oraclesByBrand;
};

export const pushPrices = async (price, brandIn, oraclesByBrand, round) => {
await waitForBlock(1);
// rotate which oracle is first. Use the round number
const oracles = oraclesByBrand.get(brandIn);
for (let i = 0; i < oracles.length; i += 1) {
const offset = (i + round) % oracles.length;
const oracle = oraclesByBrand.get(brandIn)[offset];
const oracleCmd = await agops.oracle(
'pushPriceRound',
'--price',
price,
'--oracleAdminAcceptOfferId',
oracle.offerId,
'--roundId',
round,
);
await executeOffer(oracle.address, oracleCmd);
}
};

export const getPriceQuote = async price => {
const path = `published.priceFeed.${price}-USD_price_feed`;
const body = await getQuoteBody(path);
return body.amountOut.value;
};

export const agopsInter = (...params) => {
const newParams = ['inter', ...params];
return executeCommand(agopsLocation, newParams);
};

export const createBid = (price, addr, offerId) => {
return agopsInter(
'bid',
'by-price',
`--price ${price}`,
`--give 1.0IST`,
'--from',
addr,
'--keyring-backend test',
`--offer-id ${offerId}`,
);
};

export const getLiveOffers = async addr => {
const path = `published.wallet.${addr}.current`;
const body = await getQuoteBody(path);
return body.liveOffers;
};

export const getAuctionCollateral = async index => {
const path = `published.auction.book${index}`;
const body = await getQuoteBody(path);
return body.collateralAvailable.value;
};

export const getVaultPrices = async index => {
const path = `published.vaultFactory.managers.manager${index}.quotes`;
const body = await getQuoteBody(path);
return body.quoteAmount;
};

export const bankSend = (addr, wanted) => {
const chain = ['--chain-id', CHAINID];
const from = ['--from', VALIDATORADDR];
const testKeyring = ['--keyring-backend', 'test'];
const noise = [...from, ...chain, ...testKeyring, '--yes'];

return agd.tx('bank', 'send', VALIDATORADDR, addr, wanted, ...noise);
};

export const getProvisionPoolMetrics = async () => {
const path = `published.provisionPool.metrics`;
return getQuoteBody(path);
};
27 changes: 27 additions & 0 deletions a3p-integration/proposals/a:vaults-auctions/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"agoricProposal": {
"sdk-generate": [
"vats/add-auction.js",
"vats/upgradeVaults.js"
],
"type": "/agoric.swingset.CoreEvalProposal",
"source": "subdir"
},
"type": "module",
"license": "Apache-2.0",
"dependencies": {
"@agoric/synthetic-chain": "^0.1.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]"
}
6 changes: 6 additions & 0 deletions a3p-integration/proposals/a:vaults-auctions/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 run using the executed proposal.
# The effects of this step are not persisted in further proposal layers.

yarn ava ./*.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,60 +4,42 @@ import {
agops,
ATOM_DENOM,
getISTBalance,
getVatDetails,
openVault,
USER1ADDR,
} from '@agoric/synthetic-chain';

import {
bankSend,
BID_OFFER_ID,
checkForOracle,
createBid,
generateOracleMap,
getLiveOffers,
getPriceQuote,
getVaultPrices,
pushPrices,
addPreexistingOracles,
} from './agd-tools.js';
import { getDetailsMatchingVats } from './vatDetails.js';

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);
const stOsmoDetails = await getVatDetails('stOSMO');
t.is(stOsmoDetails.incarnation, 0);
const stTiaDetails = await getVatDetails('stTIA');
t.is(stTiaDetails.incarnation, 0);
await Promise.all([
checkForOracle(t, 'ATOM'),
checkForOracle(t, 'stATOM'),
checkForOracle(t, 'stTIA'),
checkForOracle(t, 'stOSMO'),
checkForOracle(t, 'stkATOM'),
]);
};
const oraclesByBrand = new Map();

let roundId = 2;

const setupOracles = async t => {
const atomOutPre = await getPriceQuote('ATOM');
t.is(atomOutPre, '+12010000');

const BRANDNAMES = ['ATOM', 'stATOM', 'stTIA', 'stOSMO', 'stkATOM'];
const oraclesByBrand = generateOracleMap('u16', BRANDNAMES);
await addPreexistingOracles('ATOM', oraclesByBrand);

await pushPrices(11.2, 'ATOM', oraclesByBrand, roundId);
roundId += 1;
};

const checkNewQuotes = async t => {
t.log('awaiting new quotes');
const atomOut = await getPriceQuote('ATOM');
t.is(atomOut, '+11200000');
const tiaOut = await getPriceQuote('stTIA');
t.is(tiaOut, '+11300000');
const stAtomOut = await getPriceQuote('stATOM');
t.is(stAtomOut, '+11400000');
const osmoOut = await getPriceQuote('stOSMO');
t.is(osmoOut, '+11500000');
const stkAtomOut = await getPriceQuote('stkATOM');
t.is(stkAtomOut, '+11600000');
};

export const BID_OFFER_ID = 'bid-vaultUpgrade-test3';
const createNewBid = async t => {
await createBid('20', USER1ADDR, BID_OFFER_ID);
const liveOffer = await getLiveOffers(USER1ADDR);
Expand All @@ -84,14 +66,16 @@ const openMarginalVault = async t => {
};

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

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

const checkAuctionVat = async t => {
const details = await getDetailsMatchingVats('auctioneer');

// This query matches both the auction and its governor, so double the count
t.true(Object.keys(details).length > 2);
};
Expand All @@ -105,8 +89,8 @@ const verifyVaultPriceUpdate = async t => {

// 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('setup Oracles');
await setupOracles(t);

t.log('check new price quotes');
await checkNewQuotes(t);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,3 @@
# actions are executed in the upgraded chain software and the effects are
# persisted in the generated image for the upgrade, so they can be used in
# later steps, such as the "test" step, or further proposal layers.

./upgradeVaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,11 @@ export const getDetailsMatchingVats = async vatName => {
const infos = [];
for (const vatID of vatIDs) {
const vatInfo = kStore.lookupVat(vatID);
const name = vatInfo.options().name;
const source = vatInfo.source();
// @ts-expect-error cast
const { incarnation } = vatInfo.currentSpan();
infos.push({ vatName, vatID, incarnation, ...source });
infos.push({ vatName: name, vatID, incarnation, ...source });
}

return infos;
Expand Down
Loading

0 comments on commit aaa0def

Please sign in to comment.