diff --git a/a3p-integration/proposals/a:upgrade-next/priceFeed-follower-auction.test.js b/a3p-integration/proposals/a:upgrade-next/priceFeed-follower-auction.test.js index 7ac4865b2bf..37cb5ddd627 100644 --- a/a3p-integration/proposals/a:upgrade-next/priceFeed-follower-auction.test.js +++ b/a3p-integration/proposals/a:upgrade-next/priceFeed-follower-auction.test.js @@ -3,6 +3,6 @@ import { getDetailsMatchingVats } from './vatDetails.js'; test('new auction vat', async t => { const details = await getDetailsMatchingVats('auctioneer'); - // This query matches both the auction and its governor + // This query matches both the auction and its governor, so double the count t.true(Object.keys(details).length > 2); }); diff --git a/a3p-integration/proposals/a:upgrade-next/priceFeed.test.js b/a3p-integration/proposals/a:upgrade-next/priceFeed.test.js index 5dba0181116..eda8c4383b7 100644 --- a/a3p-integration/proposals/a:upgrade-next/priceFeed.test.js +++ b/a3p-integration/proposals/a:upgrade-next/priceFeed.test.js @@ -3,22 +3,37 @@ import test from 'ava'; import { agd, agops, + agopsLocation, + executeCommand, executeOffer, getVatDetails, GOV1ADDR, GOV2ADDR, GOV3ADDR, newOfferId, + openVault, + USER1ADDR } from '@agoric/synthetic-chain'; const ORACLE_ADDRESSES = [GOV1ADDR, GOV2ADDR, GOV3ADDR]; +const agdQuery = path => agd.query( + 'vstorage', + 'data', + '--output', + 'json', + path +); + +async function getQuoteBody(path) { + const queryout = await agdQuery(path); + + const body = JSON.parse(JSON.parse(queryout.value).values[0]); + return JSON.parse(body.body.substring(1)); +} + const getOracleInstance = async price => { - const instanceRec = await agd.query( - 'vstorage', - 'data', - '--output', - 'json', + const instanceRec = await agdQuery( `published.agoricNames.instance`, ); @@ -105,17 +120,9 @@ const pushPrices = (price = 10.0, brandIn) => { }; const getPriceQuote = async price => { - const priceQuote = await agd.query( - 'vstorage', - 'data', - '--output', - 'json', - `published.priceFeed.${price}-USD_price_feed`, - ); - - const body = JSON.parse(JSON.parse(priceQuote.value).values[0]); - const bodyTruncated = JSON.parse(body.body.substring(1)); - return bodyTruncated.amountOut.value; + const path = `published.priceFeed.${price}-USD_price_feed`; + const body = await getQuoteBody(path); + return body.amountOut.value; }; test.serial('push prices', async t => { @@ -148,3 +155,74 @@ test.serial('push prices', async t => { const osmoOut = await getPriceQuote('stOSMO'); t.is(osmoOut, '+11500000'); }); + +export const agopsInter = (...params) => { + const newParams = ['inter', ...params]; + return executeCommand(agopsLocation, newParams); +}; + +// agops inter bid by-price --price 1 --give 1.0IST --from $GOV1ADDR --keyring-backend test +test.serial('create new bid', async t => { + let offerId = 'bid-vaultUpgrade-test'; + const interCmd = await agopsInter( + 'bid', + 'by-price', + '--price 20', + `--give 1.0IST`, + '--from', + USER1ADDR, + '--keyring-backend test', + `--offer-id ${offerId}`, + ); + + const walletPath = `published.wallet.${USER1ADDR}.current`; + const walletOffer = await getQuoteBody(walletPath); + // | fromjson | .liveOffers[0][1].id ' + t.is(walletOffer.liveOffers[0][1].id, offerId); + +}); + +test.serial.skip('open a marginal vault', async t => { + const currentVaults = await agops.vaults('list', '--from', USER1ADDR); + + openVault(USER1ADDR, 20, 10); + + const activeVaultsAfter = await agops.vaults('list', '--from', USER1ADDR); + t.log(currentVaults, activeVaultsAfter); + t.true( + activeVaultsAfter.length > currentVaults.length, + 'vaults count increased', + ); +}); + +test.serial('trigger auction', async t => { + const startVaults = await agops.vaults('list', '--from', USER1ADDR); + await pushPrices(5.2, 'ATOM'); + + // + + // TODO(CTH): Wait long enough + // The issue is that we may need to wait two full auction cycles, and the test + // infrastructure thinks that's too long. + + // + + // agd query -o json vstorage data published.priceFeed.stOSMO-USD_price_feed |& + // jq '.value | fromjson | .values[0] | fromjson | .body[1:] | fromjson | .amountOut.value' + const atomOut = await getPriceQuote('ATOM'); + t.is(atomOut, '+5200000'); + + const book0Data = await getQuoteBody(`published.auction.book0`); + + const finishVaults = await agops.vaults('list', '--from', USER1ADDR); + t.log(startVaults, finishVaults); + t.true( + finishVaults.length < startVaults.length, + 'vaults count fell', + ); + + console.log(`auction raised`, book0Data.proceedsRaised); + + // TODO check USER1 purse for proceeds + // TODO check auction records for trade +}); diff --git a/a3p-integration/proposals/a:upgrade-next/vatDetails.js b/a3p-integration/proposals/a:upgrade-next/vatDetails.js new file mode 100644 index 00000000000..ccf24608309 --- /dev/null +++ b/a3p-integration/proposals/a:upgrade-next/vatDetails.js @@ -0,0 +1,100 @@ +import dbOpenAmbient from 'better-sqlite3'; + +const HOME = process.env.HOME; + +/** @type {(val: T | undefined) => T} */ +export const NonNullish = val => { + if (!val) throw Error('required'); + return val; +}; + +/** + * @file look up vat incarnation from kernel DB + * @see {getIncarnation} + */ + +const swingstorePath = `${HOME}/.agoric/data/agoric/swingstore.sqlite`; + +/** + * SQL short-hand + * + * @param {import('better-sqlite3').Database} db + */ +export const dbTool = db => { + const prepare = (strings, ...params) => { + const dml = strings.join('?'); + return { stmt: db.prepare(dml), params }; + }; + const sql = (strings, ...args) => { + const { stmt, params } = prepare(strings, ...args); + return stmt.all(...params); + }; + sql.get = (strings, ...args) => { + const { stmt, params } = prepare(strings, ...args); + return stmt.get(...params); + }; + return sql; +}; + +/** + * @param {import('better-sqlite3').Database} db + */ +const makeSwingstore = db => { + const sql = dbTool(db); + + /** @param {string} key */ + const kvGet = key => sql.get`select * from kvStore where key = ${key}`.value; + /** @param {string} key */ + const kvGetJSON = key => JSON.parse(kvGet(key)); + + /** @param {string} vatID */ + const lookupVat = vatID => { + return Object.freeze({ + source: () => kvGetJSON(`${vatID}.source`), + options: () => kvGetJSON(`${vatID}.options`), + currentSpan: () => + sql.get`select * from transcriptSpans where isCurrent = 1 and vatID = ${vatID}`, + }); + }; + + return Object.freeze({ + /** @param {string} vatName */ + findVat: vatName => { + /** @type {string[]} */ + const dynamicIDs = kvGetJSON('vat.dynamicIDs'); + const targetVat = dynamicIDs.find(vatID => + lookupVat(vatID).options().name.includes(vatName), + ); + if (!targetVat) throw Error(`vat not found: ${vatName}`); + return targetVat; + }, + /** @param {string} vatName */ + findVats: vatName => { + /** @type {string[]} */ + const dynamicIDs = kvGetJSON('vat.dynamicIDs'); + return dynamicIDs.filter(vatID => + lookupVat(vatID).options().name.includes(vatName), + ); + }, + lookupVat, + }); +}; + +/** @param {string} vatName */ +export const getDetailsMatchingVats = async vatName => { + const kStore = makeSwingstore( + dbOpenAmbient(swingstorePath, { readonly: true }), + ); + + const vatIDs = kStore.findVats(vatName); + const infos = []; + for (const vatID of vatIDs) { + const vatInfo = kStore.lookupVat(vatID); + const source = vatInfo.source(); + // @ts-expect-error cast + const { incarnation } = vatInfo.currentSpan(); + infos.push({ vatName, vatID, incarnation, ...source }); + } + + return infos; +};