diff --git a/a3p-integration/package.json b/a3p-integration/package.json index 3882c279d1f..fe183229c7b 100644 --- a/a3p-integration/package.json +++ b/a3p-integration/package.json @@ -12,7 +12,8 @@ "doctor": "yarn synthetic-chain doctor" }, "dependencies": { - "@agoric/synthetic-chain": "^0.0.10" + "@agoric/synthetic-chain": "^0.0.10", + "@types/better-sqlite3": "^7.6.9" }, "packageManager": "yarn@4.1.1", "license": "Apache-2.0" 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 new file mode 100644 index 00000000000..30ae903b075 --- /dev/null +++ b/a3p-integration/proposals/a:upgrade-next/priceFeed-follower-auction.test.js @@ -0,0 +1,8 @@ +import test from 'ava'; +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, so 2*2 + t.is(Object.keys(details).length, 4); +}); 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; +}; diff --git a/a3p-integration/yarn.lock b/a3p-integration/yarn.lock index 64be7d6a2ce..43335d20a3f 100644 --- a/a3p-integration/yarn.lock +++ b/a3p-integration/yarn.lock @@ -69,6 +69,24 @@ __metadata: languageName: node linkType: hard +"@types/better-sqlite3@npm:^7.6.9": + version: 7.6.9 + resolution: "@types/better-sqlite3@npm:7.6.9" + dependencies: + "@types/node": "npm:*" + checksum: 10c0/7d77add3993968982374cd73586a100fc5b9c29570a167b5798a415744983041d9ae3dcbdfd83fcf807247b777e3b8dc4e045fb7dae4a3d8484c9366ab371680 + languageName: node + linkType: hard + +"@types/node@npm:*": + version: 20.11.30 + resolution: "@types/node@npm:20.11.30" + dependencies: + undici-types: "npm:~5.26.4" + checksum: 10c0/867cfaf969c6d8850d8d7304e7ab739898a50ecb1395b61ff2335644f5f48d7a46fbc4a14cee967aed65ec134b61a746edae70d1f32f11321ccf29165e3bc4e6 + languageName: node + linkType: hard + "abbrev@npm:^2.0.0": version: 2.0.0 resolution: "abbrev@npm:2.0.0" @@ -973,6 +991,7 @@ __metadata: resolution: "root-workspace-0b6124@workspace:." dependencies: "@agoric/synthetic-chain": "npm:^0.0.10" + "@types/better-sqlite3": "npm:^7.6.9" languageName: unknown linkType: soft @@ -1190,6 +1209,13 @@ __metadata: languageName: node linkType: hard +"undici-types@npm:~5.26.4": + version: 5.26.5 + resolution: "undici-types@npm:5.26.5" + checksum: 10c0/bb673d7876c2d411b6eb6c560e0c571eef4a01c1c19925175d16e3a30c4c428181fb8d7ae802a261f283e4166a0ac435e2f505743aa9e45d893f9a3df017b501 + languageName: node + linkType: hard + "unique-filename@npm:^3.0.0": version: 3.0.0 resolution: "unique-filename@npm:3.0.0" diff --git a/golang/cosmos/app/app.go b/golang/cosmos/app/app.go index 5f0c0cb70dd..1d456668cb2 100644 --- a/golang/cosmos/app/app.go +++ b/golang/cosmos/app/app.go @@ -914,6 +914,8 @@ func unreleasedUpgradeHandler(app *GaiaApp, targetUpgrade string) func(sdk.Conte "@agoric/builders/scripts/vats/updateStOsmoPriceFeed.js", "@agoric/builders/scripts/vats/updateStTiaPriceFeed.js", ), + // Add new auction contract. The old one will be retired shortly. + vm.CoreProposalStepForModules( "@agoric/builders/scripts/vats/add-auction.js"), } } diff --git a/packages/builders/scripts/vats/add-auction.js b/packages/builders/scripts/vats/add-auction.js new file mode 100644 index 00000000000..8a248f17d17 --- /dev/null +++ b/packages/builders/scripts/vats/add-auction.js @@ -0,0 +1,14 @@ +import { makeHelpers } from '@agoric/deploy-script-support'; + +/** @type {import('@agoric/deploy-script-support/src/externalTypes.js').ProposalBuilder} */ +export const defaultProposalBuilder = async () => { + return harden({ + sourceSpec: '@agoric/inter-protocol/src/proposals/add-auction.js', + getManifestCall: ['getManifestForAddAuction'], + }); +}; + +export default async (homeP, endowments) => { + const { writeCoreProposal } = await makeHelpers(homeP, endowments); + await writeCoreProposal('add-auction', defaultProposalBuilder); +}; diff --git a/packages/inter-protocol/src/proposals/add-auction.js b/packages/inter-protocol/src/proposals/add-auction.js new file mode 100644 index 00000000000..9bc60bcdb5f --- /dev/null +++ b/packages/inter-protocol/src/proposals/add-auction.js @@ -0,0 +1,177 @@ +import { deeplyFulfilledObject, makeTracer } from '@agoric/internal'; +import { makeStorageNodeChild } from '@agoric/internal/src/lib-chainStorage.js'; +import { E } from '@endo/far'; +import { Stable } from '@agoric/internal/src/tokens.js'; +import { makeGovernedTerms as makeGovernedATerms } from '../auction/params.js'; + +const trace = makeTracer('NewAuction', true); + +/** @param {import('./econ-behaviors.js').EconomyBootstrapPowers} powers */ +export const addAuction = async ({ + consume: { + zoe, + board, + chainTimerService, + priceAuthority, + chainStorage, + economicCommitteeCreatorFacet: electorateCreatorFacet, + auctioneerKit: legacyKitP, + }, + produce: { newAuctioneerKit }, + instance: { + consume: { reserve: reserveInstance }, + }, + installation: { + consume: { + auctioneer: auctionInstallation, + contractGovernor: contractGovernorInstallation, + }, + }, + issuer: { + consume: { [Stable.symbol]: stableIssuerP }, + }, +}) => { + trace('addAuction start'); + const STORAGE_PATH = 'auction'; + + const poserInvitationP = E(electorateCreatorFacet).getPoserInvitation(); + + const [ + initialPoserInvitation, + electorateInvitationAmount, + stableIssuer, + legacyKit, + ] = await Promise.all([ + poserInvitationP, + E(E(zoe).getInvitationIssuer()).getAmountOf(poserInvitationP), + stableIssuerP, + legacyKitP, + ]); + + // Each field has an extra layer of type + value: + // AuctionStartDelay: { type: 'relativeTime', value: { relValue: 2n, timerBrand: Object [Alleged: timerBrand] {} } } + /** @type {any} */ + const paramValues = await E(legacyKit.publicFacet).getGovernedParams(); + const params = harden({ + StartFrequency: paramValues.StartFrequency.value, + ClockStep: paramValues.ClockStep.value, + StartingRate: paramValues.StartingRate.value, + LowestRate: paramValues.LowestRate.value, + DiscountStep: paramValues.DiscountStep.value, + AuctionStartDelay: paramValues.AuctionStartDelay.value, + PriceLockPeriod: paramValues.PriceLockPeriod.value, + }); + const timerBrand = await E(chainTimerService).getTimerBrand(); + + const storageNode = await makeStorageNodeChild(chainStorage, STORAGE_PATH); + const marshaller = await E(board).getReadonlyMarshaller(); + + const reservePublicFacet = await E(zoe).getPublicFacet(reserveInstance); + + const auctionTerms = makeGovernedATerms( + { storageNode, marshaller }, + chainTimerService, + priceAuthority, + reservePublicFacet, + { + ...params, + ElectorateInvitationAmount: electorateInvitationAmount, + TimerBrand: timerBrand, + }, + ); + + const governorTerms = await deeplyFulfilledObject( + harden({ + timer: chainTimerService, + governedContractInstallation: auctionInstallation, + governed: { + terms: auctionTerms, + issuerKeywordRecord: { Bid: stableIssuer }, + storageNode, + marshaller, + label: 'auctioneer', + }, + }), + ); + + /** @type {GovernorStartedInstallationKit} */ + const governorStartResult = await E(zoe).startInstance( + contractGovernorInstallation, + undefined, + governorTerms, + harden({ + electorateCreatorFacet, + governed: { + initialPoserInvitation, + storageNode, + marshaller, + }, + }), + 'auctioneer.governor', + ); + + const [governedInstance, governedCreatorFacet, governedPublicFacet] = + await Promise.all([ + E(governorStartResult.creatorFacet).getInstance(), + E(governorStartResult.creatorFacet).getCreatorFacet(), + E(governorStartResult.creatorFacet).getPublicFacet(), + ]); + + const allIssuers = await E(zoe).getIssuers(legacyKit.instance); + const { Bid: _istIssuer, ...auctionIssuers } = allIssuers; + await Promise.all( + Object.keys(auctionIssuers).map(kwd => + E(governedCreatorFacet).addBrand(auctionIssuers[kwd], kwd), + ), + ); + + // don't overwrite auctioneerKit yet + newAuctioneerKit.resolve( + harden({ + label: 'auctioneer', + creatorFacet: governedCreatorFacet, + adminFacet: governorStartResult.adminFacet, + publicFacet: governedPublicFacet, + instance: governedInstance, + + governor: governorStartResult.instance, + governorCreatorFacet: governorStartResult.creatorFacet, + governorAdminFacet: governorStartResult.adminFacet, + }), + ); + // don't replace auction instance yet. +}; + +export const ADD_AUCTION_MANIFEST = harden({ + [addAuction.name]: { + consume: { + zoe: true, + board: true, + chainTimerService: true, + priceAuthority: true, + chainStorage: true, + economicCommitteeCreatorFacet: true, + auctioneerKit: true, + }, + produce: { + newAuctioneerKit: true, + }, + instance: { + consume: { reserve: true }, + }, + installation: { + consume: { + auctioneer: true, + contractGovernor: true, + }, + }, + issuer: { + consume: { [Stable.symbol]: true }, + }, + }, +}); + +/* Add a new auction to a chain that already has one. */ +export const getManifestForAddAuction = async () => { + return { manifest: ADD_AUCTION_MANIFEST }; +}; diff --git a/packages/inter-protocol/src/proposals/econ-behaviors.js b/packages/inter-protocol/src/proposals/econ-behaviors.js index 5b669fd31a4..a63685a9017 100644 --- a/packages/inter-protocol/src/proposals/econ-behaviors.js +++ b/packages/inter-protocol/src/proposals/econ-behaviors.js @@ -64,6 +64,7 @@ export const SECONDS_PER_WEEK = 7n * SECONDS_PER_DAY; * >; * vaultFactoryKit: GovernanceFacetKit; * auctioneerKit: AuctioneerKit; + * newAuctioneerKit: AuctioneerKit; * minInitialDebt: NatValue; * }>} EconomyBootstrapSpace */