From d864b666104beccf5f5ccad222f7a5d23a5ad7d5 Mon Sep 17 00:00:00 2001 From: Chip Morningstar Date: Sun, 3 Dec 2023 20:00:33 -0800 Subject: [PATCH 1/3] feat: implement benchmarks for price feeds with and without liquidation. This includes adjustments to the boot tooling to accommodate its use for benchmarking, as well as improvements and enhancements to the bechmarkerator API that were required to make these benchmarks possible. Closes #8496 --- .../benchmark/benchmark-liquidation.js | 233 ++++++++++++++++++ .../benchmark/benchmark-pricefeed.js | 40 +++ packages/benchmark/doc/benchmarkerator.md | 19 ++ packages/benchmark/src/benchmarkerator.js | 232 ++++++++++++----- .../test/bootstrapTests/test-addAssets.ts | 2 +- .../test/bootstrapTests/test-liquidation-1.ts | 2 +- .../bootstrapTests/test-liquidation-2b.ts | 2 +- .../test-liquidation-concurrent-1.ts | 2 +- .../test-liquidation-concurrent-2b.ts | 2 +- .../test-walletSurvivesZoeRestart.ts | 2 +- packages/boot/tools/drivers.ts | 1 + .../bootstrapTests => tools}/liquidation.ts | 167 +++++++------ .../decentral-itest-vaults-config.json | 37 ++- 13 files changed, 596 insertions(+), 145 deletions(-) create mode 100644 packages/benchmark/benchmark/benchmark-liquidation.js create mode 100644 packages/benchmark/benchmark/benchmark-pricefeed.js rename packages/boot/{test/bootstrapTests => tools}/liquidation.ts (85%) diff --git a/packages/benchmark/benchmark/benchmark-liquidation.js b/packages/benchmark/benchmark/benchmark-liquidation.js new file mode 100644 index 00000000000..bfeb0da0a17 --- /dev/null +++ b/packages/benchmark/benchmark/benchmark-liquidation.js @@ -0,0 +1,233 @@ +// eslint-disable-next-line import/order +import { bench } from '../src/benchmarkerator.js'; + +import { Offers } from '@agoric/inter-protocol/src/clientSupport.js'; +import { scale6 } from '@agoric/boot/tools/liquidation.ts'; + +const setupData = { + vaults: [ + { + atom: 15, + ist: 100, + debt: 100.5, + }, + ], + bids: [ + { + give: '80IST', + discount: 0.1, + }, + ], + price: { + starting: 12.34, + trigger: 9.99, + }, + auction: { + start: { + collateral: 15, + debt: 100.5, + }, + end: { + collateral: 9.659301, + debt: 0, + }, + }, +}; + +const outcome = { + bids: [ + { + payouts: { + Bid: 0, + Collateral: 8.897786, + }, + }, + ], + reserve: { + allocations: { + ATOM: 6.102214, + }, + shortfall: 20.5, + }, + vaultsSpec: [ + { + locked: 3.373, + }, + ], + vaultsActual: [ + { + locked: 3.525747, + }, + ], +}; + +const liquidationOptions = { + collateralBrandKey: 'ATOM', + managerIndex: 0, +}; + +const setup = async context => { + const { managerIndex } = liquidationOptions; + const { walletFactoryDriver } = context.tools; + + const metricsPath = `published.vaultFactory.managers.manager${managerIndex}.metrics`; + const buyer = await walletFactoryDriver.provideSmartWallet('agoric1buyer'); + const minter = await walletFactoryDriver.provideSmartWallet('agoric1minter'); + + return { metricsPath, buyer, minter }; +}; + +const setupRound = async (context, round) => { + const { collateralBrandKey, managerIndex } = liquidationOptions; + const { setupVaults, placeBids } = context.tools; + + await setupVaults( + collateralBrandKey, + managerIndex, + setupData, + round * setupData.vaults.length, + ); + await placeBids(collateralBrandKey, 'agoric1buyer', setupData); +}; + +const executeRound = async (context, round) => { + const { collateralBrandKey, managerIndex } = liquidationOptions; + const { advanceTimeBy, advanceTimeTo, priceFeedDrivers, readLatest } = + context.tools; + + const { metricsPath } = context.config; + + // --------------- + // Change price to trigger liquidation + // --------------- + + await priceFeedDrivers[collateralBrandKey].setPrice(9.99); + + // check nothing liquidating yet + const liveSchedule = readLatest('published.auction.schedule'); + assert(liveSchedule.activeStartTime === null); + const metrics1 = readLatest(metricsPath); + assert( + metrics1.numActiveVaults === setupData.vaults.length && + metrics1.numLiquidatingVaults === 0, + ); + + // advance time to start an auction + console.log(collateralBrandKey, 'step 1 of 10'); + await advanceTimeTo(liveSchedule.nextDescendingStepTime); + const metrics2 = readLatest(metricsPath); + assert( + metrics2.numActiveVaults === 0 && + metrics2.numLiquidatingVaults === setupData.vaults.length && + metrics2.liquidatingCollateral.value === + scale6(setupData.auction.start.collateral) && + metrics2.liquidatingDebt.value === scale6(setupData.auction.start.debt) && + metrics2.lockedQuote === null, + ); + + console.log(collateralBrandKey, 'step 2 of 10'); + await advanceTimeBy(3, 'minutes'); + let auctionBook = readLatest(`published.auction.book${managerIndex}`); + assert( + auctionBook.collateralAvailable.value === + scale6(setupData.auction.start.collateral) && + auctionBook.startCollateral.value === + scale6(setupData.auction.start.collateral) && + auctionBook.startProceedsGoal.value === + scale6(setupData.auction.start.debt), + ); + + console.log(collateralBrandKey, 'step 3 of 10'); + await advanceTimeBy(3, 'minutes'); + + console.log(collateralBrandKey, 'step 4 of 10'); + await advanceTimeBy(3, 'minutes'); + + let buyerWallet = readLatest('published.wallet.agoric1buyer'); + assert( + buyerWallet.status.id === `${collateralBrandKey}-bid1` && + buyerWallet.status.payouts.Collateral.value === + scale6(outcome.bids[0].payouts.Collateral) && + buyerWallet.status.payouts.Bid.value === + scale6(outcome.bids[0].payouts.Bid), + ); + + console.log(collateralBrandKey, 'step 5 of 10'); + await advanceTimeBy(3, 'minutes'); + + console.log(collateralBrandKey, 'step 6 of 10'); + await advanceTimeBy(3, 'minutes'); + auctionBook = readLatest(`published.auction.book${managerIndex}`); + assert(auctionBook.collateralAvailable.value === 6102214n); + + console.log(collateralBrandKey, 'step 7 of 10'); + await advanceTimeBy(3, 'minutes'); + + console.log(collateralBrandKey, 'step 8 of 10'); + await advanceTimeBy(3, 'minutes'); + + console.log(collateralBrandKey, 'step 9 of 10'); + await advanceTimeBy(3, 'minutes'); + + const metrics3 = readLatest(metricsPath); + const roundB = BigInt(round); + assert( + metrics3.numActiveVaults === 0 && + metrics3.numLiquidationsCompleted === setupData.vaults.length * round && + metrics3.numLiquidatingVaults === 0 && + metrics3.retainedCollateral.value === 0n && + metrics3.totalCollateral.value === 0n && + metrics3.totalCollateralSold.value === 8897786n * roundB && + metrics3.totalDebt.value === 0n && + metrics3.totalOverageReceived.value === 0n && + metrics3.totalProceedsReceived.value === 80000000n * roundB && + metrics3.totalShortfallReceived.value === 20500000n * roundB, + ); + + console.log(collateralBrandKey, 'step 10 of 10'); + + buyerWallet = readLatest('published.wallet.agoric1buyer'); + assert( + buyerWallet.status.id === `${collateralBrandKey}-bid1` && + buyerWallet.status.payouts.Collateral.value === + scale6(outcome.bids[0].payouts.Collateral) && + buyerWallet.status.payouts.Bid.value === + scale6(outcome.bids[0].payouts.Bid), + ); + + // check reserve balances + const metrics4 = readLatest('published.reserve.metrics'); + assert( + metrics4.allocations[collateralBrandKey].value === + scale6(outcome.reserve.allocations[collateralBrandKey]) * roundB && + metrics4.shortfallBalance.value === + scale6(outcome.reserve.shortfall) * roundB, + ); +}; + +const finishRound = async (context, round) => { + const { collateralBrandKey } = liquidationOptions; + const { minter } = context.config; + + await 47; // sacrifice to the gods of asynchrony + for (let i = 0; i < setupData.vaults.length; i += 1) { + await minter.executeOfferMaker( + Offers.vaults.CloseVault, + { + offerId: `${collateralBrandKey}-bid${i}`, + collateralBrandKey, + giveMinted: 0, + }, + `open-${collateralBrandKey}-vault${round * setupData.vaults.length + i}`, + ); + } +}; + +bench.addBenchmark('price feed with liquidation', { + setup, + setupRound, + executeRound, + finishRound, +}); + +await bench.run(); diff --git a/packages/benchmark/benchmark/benchmark-pricefeed.js b/packages/benchmark/benchmark/benchmark-pricefeed.js new file mode 100644 index 00000000000..bd1ca1154f8 --- /dev/null +++ b/packages/benchmark/benchmark/benchmark-pricefeed.js @@ -0,0 +1,40 @@ +import { bench } from '../src/benchmarkerator.js'; + +const setupData = { + vaults: [], + price: { starting: 12.34 }, +}; + +const liquidationOptions = { + collateralBrandKey: 'ATOM', + managerIndex: 0, +}; + +const setup = async context => { + const { collateralBrandKey, managerIndex } = liquidationOptions; + const { setupVaults } = context.tools; + + await setupVaults( + collateralBrandKey, + managerIndex, + setupData, + setupData.vaults.length, + ); + + return {}; +}; + +const executeRound = async (context, round) => { + const { collateralBrandKey } = liquidationOptions; + const { priceFeedDrivers } = context.tools; + + console.log(`price feed round #${round}`); + await priceFeedDrivers[collateralBrandKey].setPrice(9.99); +}; + +bench.addBenchmark('price feed without liquidation', { + setup, + executeRound, +}); + +await bench.run(); diff --git a/packages/benchmark/doc/benchmarkerator.md b/packages/benchmark/doc/benchmarkerator.md index 129b5bb149b..11cf5f9bade 100644 --- a/packages/benchmark/doc/benchmarkerator.md +++ b/packages/benchmark/doc/benchmarkerator.md @@ -84,6 +84,25 @@ optional except for `executeRound`: benchmark. The `round` argument is the number of the benchmark round that this call to `executeRound` is being asked to execute (counting from 1). +`setupRound: (context: BenchmarkContext, round: number) => Promise` + + A optional async method which is called to perform initializations required + for a single round of the benchmark. It is called immediately before calling + `executeRound` but its execution time and any resources it consumes will not + be accounted against the stats for the round in question. The `round` + argument is the number of the benchmark round that the corresponding call to + `executeRound` is being asked to execute (counting from 1). + +`finishRound: (context: BenchmarkContext, round: number) => Promise` + + A optional async method which is called to perform teardowns required by a + single round of the benchmark. It is called immediately after calling + `executeRound` but its execution time and any resources it consumes will not + be accounted against the stats for the round in question (or the round that + follows). The `round` argument is the number of the benchmark round that the + corresponding call to `executeRound` that just completed execution (counting + from 1). + `finish?: (context: BenchmarkContext) => Promise` An optional async method to perform any post-run teardown that you need to do. diff --git a/packages/benchmark/src/benchmarkerator.js b/packages/benchmark/src/benchmarkerator.js index ac0d6e47a67..d648ccbe5ff 100644 --- a/packages/benchmark/src/benchmarkerator.js +++ b/packages/benchmark/src/benchmarkerator.js @@ -14,7 +14,11 @@ import { Fail } from '@agoric/assert'; import { eventLoopIteration } from '@agoric/internal/src/testing-utils.js'; import { makeAgoricNamesRemotesFromFakeStorage } from '@agoric/vats/tools/board-utils.js'; import { makeSwingsetTestKit } from '@agoric/boot/tools/supports.ts'; -import { makeWalletFactoryDriver } from '@agoric/boot/tools/drivers.ts'; +import { + makeWalletFactoryDriver, + makeGovernanceDriver, +} from '@agoric/boot/tools/drivers.ts'; +import { makeLiquidationTestKit } from '@agoric/boot/tools/liquidation.ts'; // When I was a child my family took a lot of roadtrips around California to go // camping and backpacking and so on. It was not uncommon in those days (nor is @@ -45,6 +49,7 @@ import { makeWalletFactoryDriver } from '@agoric/boot/tools/drivers.ts'; * options: Record, * argv: string[], * actors: Record, + * tools: Record, * title?: string, * rounds?: number, * config?: Record, @@ -67,6 +72,9 @@ import { makeWalletFactoryDriver } from '@agoric/boot/tools/drivers.ts'; * // The label string for the benchmark currently being executed * title: string, * + * // Functions provided to do things + * tools: Record, + * * // The number of rounds of this benchmark that will be executed * rounds: number, * @@ -79,7 +87,9 @@ import { makeWalletFactoryDriver } from '@agoric/boot/tools/drivers.ts'; * * @typedef {{ * setup?: (context: BenchmarkContext) => Promise | undefined>, + * setupRound?: (context: BenchmarkContext, round: number) => Promise, * executeRound: (context: BenchmarkContext, round: number) => Promise, + * finishRound?: (context: BenchmarkContext, round: number) => Promise, * finish?: (context: BenchmarkContext) => Promise, * rounds?: number, * }} Benchmark @@ -90,6 +100,22 @@ import { makeWalletFactoryDriver } from '@agoric/boot/tools/drivers.ts'; * // benchmark context parameter of subsequent calls into the benchmark object. * setup?: (context: BenchmarkContext) => Promise | undefined>, * + * // Optional setup method for an individual round. This is executed as part + * // of a benchmark round, just prior to calling the `executeRound` method, + * // but does not count towards the timing or resource usage statistics for + * // the round itself. Use this when there is per-round initialization that + * // is necessary to execute the round but which should not be considered + * // part of the operation being measured. + * setupRound?: (context: BenchmarkContext, round: number) => Promise, + * + * // Optional finish method for an individual round. This is executed as + * // part of a benchmark round, just after calling the `executeRound` method, + * // but does not count towards the timing or resource usage statistics for + * // the round itself. Use this when there is per-round teardown that is + * // necessary after executing the round but which should not be considered + * // part of the operation being measured. + * finishRound?: (context: BenchmarkContext, round: number) => Promise, + * * // Run one round of the benchmark * executeRound: (context: BenchmarkContext, round: number) => Promise, * @@ -117,7 +143,7 @@ const argv = process.argv.slice(2); let commandLineRounds; let verbose = false; let help = false; -let dumpFile; +let outputFile; let slogFile; /** @type ManagerType */ let defaultManagerType = 'xs-worker'; @@ -204,7 +230,7 @@ while (argv[0] && stillScanningArgs) { break; case '-o': case '--output': - dumpFile = argv.shift(); + outputFile = argv.shift(); break; case '--vat-type': { const type = argv.shift(); @@ -399,18 +425,6 @@ const organizeSetupStats = (rawStats, elapsedTime, cranks) => { }; const printBenchmarkStats = stats => { - const w1 = 32; - const h1 = `${'Stat'.padEnd(w1)}`; - const d1 = `${''.padEnd(w1, '-')}`; - - const w2 = 6; - const h2 = `${'Delta'.padStart(w2)}`; - const d2 = ` ${''.padStart(w2 - 1, '-')}`; - - const w3 = 10; - const h3 = `${'PerRound'.padStart(w3)}`; - const d3 = ` ${''.padStart(w3 - 1, '-')}`; - const cpr = pn(stats.cranksPerRound).trim(); log( `${stats.cranks} cranks over ${stats.rounds} rounds (${cpr} cranks/round)`, @@ -425,25 +439,64 @@ const printBenchmarkStats = stats => { // prettier-ignore `${stats.rounds} rounds in ${stats.elapsedTime}ns (${stats.timePerRound.toFixed(3)}/round})`, ); - log(`${h1} ${h2} ${h3}`); - log(`${d1} ${d2} ${d3}`); - const data = stats.data; - for (const [key, entry] of Object.entries(data)) { - const col1 = `${key.padEnd(w1)}`; - const col2 = `${String(entry.delta).padStart(w2)}`; - const col3 = `${pn(entry.deltaPerRound).padStart(w3)}`; + const wc1 = 32; + const hc1 = `${'Counter'.padEnd(wc1)}`; + const dc1 = `${''.padEnd(wc1, '-')}`; + + const wc2 = 6; + const hc2 = `${'Delta'.padStart(wc2)}`; + const dc2 = ` ${''.padStart(wc2 - 1, '-')}`; + + const wc3 = 10; + const hc3 = `${'PerRound'.padStart(wc3)}`; + const dc3 = ` ${''.padStart(wc3 - 1, '-')}`; + + log(``); + log(`${hc1} ${hc2} ${hc3}`); + log(`${dc1} ${dc2} ${dc3}`); + for (const [key, entry] of Object.entries(stats.counters)) { + const col1 = `${key.padEnd(wc1)}`; + const col2 = `${String(entry.delta).padStart(wc2)}`; + const col3 = `${pn(entry.deltaPerRound).padStart(wc3)}`; log(`${col1} ${col2} ${col3}`); } + + const wg1 = 32; + const hg1 = `${'Gauge'.padEnd(wg1)}`; + const dg1 = `${''.padEnd(wg1, '-')}`; + + const wg2 = 6; + const hg2 = `${'Start'.padStart(wg2)}`; + const dg2 = ` ${''.padStart(wg2 - 1, '-')}`; + + const wg3 = 6; + const hg3 = `${'End'.padStart(wg3)}`; + const dg3 = ` ${''.padStart(wg3 - 1, '-')}`; + + const wg4 = 6; + const hg4 = `${'Delta'.padStart(wg4)}`; + const dg4 = ` ${''.padStart(wg4 - 1, '-')}`; + + log(``); + log(`${hg1} ${hg2} ${hg3} ${hg4}`); + log(`${dg1} ${dg2} ${dg3} ${dg4}`); + for (const [key, entry] of Object.entries(stats.gauges)) { + const col1 = `${key.padEnd(wg1)}`; + const col2 = `${String(entry.start).padStart(wg2)}`; + const col3 = `${String(entry.end).padStart(wg3)}`; + const col4 = `${String(entry.end - entry.start).padStart(wg4)}`; + log(`${col1} ${col2} ${col3} ${col4}`); + } }; -const organizeRoundsStats = ( - rawBefore, - rawAfter, - elapsedTime, - cranks, - rounds, -) => { +const organizeRoundsStats = (rounds, perRoundStats) => { + let elapsedTime = 0; + let cranks = 0; + for (const { start, end } of perRoundStats) { + elapsedTime += Number(end.time - start.time); + cranks += end.cranks - start.cranks; + } const stats = { elapsedTime, cranks, @@ -451,17 +504,31 @@ const organizeRoundsStats = ( timePerCrank: cranks ? elapsedTime / cranks : 0, timePerRound: elapsedTime / rounds, cranksPerRound: cranks / rounds, - data: {}, + perRoundStats, + counters: {}, + gauges: {}, }; - // Note: the following assumes rawBefore and rawAfter have the same keys. - for (const [key, value] of Object.entries(rawBefore)) { + // Note: the following assumes stats records all have the same keys. + const first = perRoundStats[0].start.rawStats; + const last = perRoundStats[perRoundStats.length - 1].end.rawStats; + for (const key of Object.keys(first)) { if (isMainKey(key)) { - const delta = rawAfter[key] - value; - stats.data[key] = { - delta, - deltaPerRound: delta / rounds, - }; + if (Object.hasOwn(first, `${key}Max`)) { + stats.gauges[key] = { + start: first[key], + end: last[key], + }; + } else { + let delta = 0; + for (const { start, end } of perRoundStats) { + delta += Number(end.rawStats[key] - start.rawStats[key]); + } + stats.counters[key] = { + delta, + deltaPerRound: delta / rounds, + }; + } } } return stats; @@ -509,16 +576,26 @@ export const makeBenchmarkerator = async () => { agoricNamesRemotes, ); - const actors = { - gov1: await walletFactoryDriver.provideSmartWallet( + const governanceDriver = await makeGovernanceDriver( + swingsetTestKit, + agoricNamesRemotes, + walletFactoryDriver, + [ 'agoric1ldmtatp24qlllgxmrsjzcpe20fvlkp448zcuce', - ), - gov2: await walletFactoryDriver.provideSmartWallet( 'agoric140dmkrz2e42ergjj7gyvejhzmjzurvqeq82ang', - ), - gov3: await walletFactoryDriver.provideSmartWallet( 'agoric1w8wktaur4zf8qmmtn3n7x3r0jhsjkjntcm3u6h', - ), + ], + ); + + const liquidationTestKit = await makeLiquidationTestKit({ + swingsetTestKit, + agoricNamesRemotes, + walletFactoryDriver, + governanceDriver, + like: () => {}, + }); + + const actors = { atom1: await walletFactoryDriver.provideSmartWallet( 'agoric1ldmtatp24qlllgxmrsjzcpe20fvlkp448zcuce', ), @@ -529,6 +606,11 @@ export const makeBenchmarkerator = async () => { bob: await walletFactoryDriver.provideSmartWallet('agoric1bob'), carol: await walletFactoryDriver.provideSmartWallet('agoric1carol'), }; + let idx = 1; + for (const gov of governanceDriver.ecMembers) { + actors[`gov${idx}`] = gov; + idx += 1; + } const setupEndTime = readClock(); const setupCranks = getCrankNumber(); @@ -544,8 +626,13 @@ export const makeBenchmarkerator = async () => { printSetupStats(setupStats); log('-'.repeat(70)); const benchmarkReport = { setup: setupStats }; + const tools = { + ...swingsetTestKit, + ...liquidationTestKit, + walletFactoryDriver, + }; - const context = harden({ options, argv, actors }); + const context = { options, argv, actors, tools }; /** * Add a benchmark to the set being run by an execution of the benchmark @@ -561,8 +648,16 @@ export const makeBenchmarkerator = async () => { typeof benchmark.setup === 'function' || Fail`benchmark setup must be a function`; } + if (benchmark.setupRound) { + typeof benchmark.setupRound === 'function' || + Fail`benchmark setupRound must be a function`; + } benchmark.executeRound || Fail`no executeRound function for benchmark ${title}`; + if (benchmark.finishRound) { + typeof benchmark.finishRound === 'function' || + Fail`benchmark finishRound must be a function`; + } typeof benchmark.executeRound === 'function' || Fail`benchmark executeRound must be a function`; if (benchmark.finish) { @@ -594,25 +689,31 @@ export const makeBenchmarkerator = async () => { benchmarkContext.config = await (benchmark.setup ? benchmark.setup(benchmarkContext) : {}); - const roundsStartRawStats = controller.getStats(); - const roundsStartTime = readClock(); - const cranksAtRoundsStart = getCrankNumber(); + log(`Benchmark "${title}" setup complete`); + log(`------------------------------------------------------------------`); + const perRoundStats = []; for (let round = 1; round <= rounds; round += 1) { + if (benchmark.setupRound) { + await benchmark.setupRound(benchmarkContext, round); + } + const start = { + rawStats: controller.getStats(), + time: readClock(), + cranks: getCrankNumber(), + }; log(`Benchmark "${title}" round ${round}:`); await benchmark.executeRound(benchmarkContext, round); + const end = { + rawStats: controller.getStats(), + time: readClock(), + cranks: getCrankNumber(), + }; + perRoundStats.push({ start, end }); + if (benchmark.finishRound) { + await benchmark.finishRound(benchmarkContext, round); + } } - const roundsEndRawStats = controller.getStats(); - const roundsEndTime = readClock(); - const cranksAtRoundsEnd = getCrankNumber(); - const cranksDuringRounds = cranksAtRoundsEnd - cranksAtRoundsStart; - const roundsElapsedTime = Number(roundsEndTime - roundsStartTime); - const benchmarkStats = organizeRoundsStats( - roundsStartRawStats, - roundsEndRawStats, - roundsElapsedTime, - cranksDuringRounds, - rounds, - ); + const benchmarkStats = organizeRoundsStats(rounds, perRoundStats); log('-'.repeat(70)); log(`Benchmark "${title}" stats:`); printBenchmarkStats(benchmarkStats); @@ -624,8 +725,15 @@ export const makeBenchmarkerator = async () => { } await eventLoopIteration(); await shutdown(); - if (dumpFile) { - fs.writeFileSync(dumpFile, JSON.stringify(benchmarkReport, null, 2)); + if (outputFile) { + fs.writeFileSync( + outputFile, + JSON.stringify( + benchmarkReport, + (_key, value) => (typeof value === 'bigint' ? Number(value) : value), + 2, + ), + ); } }; diff --git a/packages/boot/test/bootstrapTests/test-addAssets.ts b/packages/boot/test/bootstrapTests/test-addAssets.ts index eecdaffe601..3494f0a6a56 100644 --- a/packages/boot/test/bootstrapTests/test-addAssets.ts +++ b/packages/boot/test/bootstrapTests/test-addAssets.ts @@ -6,7 +6,7 @@ import { TimeMath } from '@agoric/time'; import { LiquidationTestContext, makeLiquidationTestContext, -} from './liquidation.ts'; +} from '../../tools/liquidation.ts'; import { makeProposalExtractor } from '../../tools/supports.ts'; const test = anyTest as TestFn< diff --git a/packages/boot/test/bootstrapTests/test-liquidation-1.ts b/packages/boot/test/bootstrapTests/test-liquidation-1.ts index 7d3794528d4..b9922456dc0 100644 --- a/packages/boot/test/bootstrapTests/test-liquidation-1.ts +++ b/packages/boot/test/bootstrapTests/test-liquidation-1.ts @@ -12,7 +12,7 @@ import { makeLiquidationTestContext, scale6, LiquidationSetup, -} from './liquidation.ts'; +} from '../../tools/liquidation.ts'; const test = anyTest as TestFn; diff --git a/packages/boot/test/bootstrapTests/test-liquidation-2b.ts b/packages/boot/test/bootstrapTests/test-liquidation-2b.ts index 2225e165e1a..e9c0bc2a361 100644 --- a/packages/boot/test/bootstrapTests/test-liquidation-2b.ts +++ b/packages/boot/test/bootstrapTests/test-liquidation-2b.ts @@ -17,7 +17,7 @@ import { LiquidationTestContext, makeLiquidationTestContext, scale6, -} from './liquidation.ts'; +} from '../../tools/liquidation.ts'; const test = anyTest as TestFn; diff --git a/packages/boot/test/bootstrapTests/test-liquidation-concurrent-1.ts b/packages/boot/test/bootstrapTests/test-liquidation-concurrent-1.ts index 886cf9d212f..e6ee036f4fb 100644 --- a/packages/boot/test/bootstrapTests/test-liquidation-concurrent-1.ts +++ b/packages/boot/test/bootstrapTests/test-liquidation-concurrent-1.ts @@ -12,7 +12,7 @@ import { likePayouts, makeLiquidationTestContext, scale6, -} from './liquidation.ts'; +} from '../../tools/liquidation.ts'; const test = anyTest as TestFn; diff --git a/packages/boot/test/bootstrapTests/test-liquidation-concurrent-2b.ts b/packages/boot/test/bootstrapTests/test-liquidation-concurrent-2b.ts index 62f7c597644..19677638b5b 100644 --- a/packages/boot/test/bootstrapTests/test-liquidation-concurrent-2b.ts +++ b/packages/boot/test/bootstrapTests/test-liquidation-concurrent-2b.ts @@ -15,7 +15,7 @@ import { ensureVaultCollateral, makeLiquidationTestContext, scale6, -} from './liquidation.ts'; +} from '../../tools/liquidation.ts'; const test = anyTest as TestFn; diff --git a/packages/boot/test/bootstrapTests/test-walletSurvivesZoeRestart.ts b/packages/boot/test/bootstrapTests/test-walletSurvivesZoeRestart.ts index 60a5b36e7b2..910234eedd9 100644 --- a/packages/boot/test/bootstrapTests/test-walletSurvivesZoeRestart.ts +++ b/packages/boot/test/bootstrapTests/test-walletSurvivesZoeRestart.ts @@ -12,7 +12,7 @@ import { makeLiquidationTestContext, scale6, LiquidationSetup, -} from './liquidation.ts'; +} from '../../tools/liquidation.ts'; const test = anyTest as TestFn; diff --git a/packages/boot/tools/drivers.ts b/packages/boot/tools/drivers.ts index fb04aa0972b..fe51d2b3769 100644 --- a/packages/boot/tools/drivers.ts +++ b/packages/boot/tools/drivers.ts @@ -315,6 +315,7 @@ export const makeGovernanceDriver = async ( await enactLatestProposal(); await testKit.advanceTimeBy(1, 'minutes'); }, + ecMembers, }; }; diff --git a/packages/boot/test/bootstrapTests/liquidation.ts b/packages/boot/tools/liquidation.ts similarity index 85% rename from packages/boot/test/bootstrapTests/liquidation.ts rename to packages/boot/tools/liquidation.ts index e7ec6ce39cc..117753dd5db 100644 --- a/packages/boot/test/bootstrapTests/liquidation.ts +++ b/packages/boot/tools/liquidation.ts @@ -9,12 +9,12 @@ import { } from '@agoric/vats/tools/board-utils.js'; import { Offers } from '@agoric/inter-protocol/src/clientSupport.js'; import type { ExecutionContext } from 'ava'; -import { makeSwingsetTestKit } from '../../tools/supports.ts'; +import { makeSwingsetTestKit } from './supports.ts'; import { makeGovernanceDriver, makePriceFeedDriver, makeWalletFactoryDriver, -} from '../../tools/drivers.ts'; +} from './drivers.ts'; export type LiquidationSetup = { vaults: { @@ -63,52 +63,13 @@ export const likePayouts = ({ Bid, Collateral }) => ({ }, }); -export const makeLiquidationTestContext = async t => { - console.time('DefaultTestContext'); - const swingsetTestKit = await makeSwingsetTestKit(t.log, 'bundles/vaults', { - configSpecifier: '@agoric/vm-config/decentral-main-vaults-config.json', - }); - - const { runUtils, storage } = swingsetTestKit; - console.timeLog('DefaultTestContext', 'swingsetTestKit'); - const { EV } = runUtils; - - // Wait for ATOM to make it into agoricNames - await EV.vat('bootstrap').consumeItem('vaultFactoryKit'); - console.timeLog('DefaultTestContext', 'vaultFactoryKit'); - - // has to be late enough for agoricNames data to have been published - const agoricNamesRemotes: AgoricNamesRemotes = - makeAgoricNamesRemotesFromFakeStorage(swingsetTestKit.storage); - const refreshAgoricNamesRemotes = () => { - Object.assign( - agoricNamesRemotes, - makeAgoricNamesRemotesFromFakeStorage(swingsetTestKit.storage), - ); - }; - agoricNamesRemotes.brand.ATOM || Fail`ATOM missing from agoricNames`; - console.timeLog('DefaultTestContext', 'agoricNamesRemotes'); - - const walletFactoryDriver = await makeWalletFactoryDriver( - runUtils, - storage, - agoricNamesRemotes, - ); - console.timeLog('DefaultTestContext', 'walletFactoryDriver'); - - const governanceDriver = await makeGovernanceDriver( - swingsetTestKit, - agoricNamesRemotes, - walletFactoryDriver, - // TODO read from the config file - [ - 'agoric1gx9uu7y6c90rqruhesae2t7c2vlw4uyyxlqxrx', - 'agoric1d4228cvelf8tj65f4h7n2td90sscavln2283h5', - 'agoric14543m33dr28x7qhwc558hzlj9szwhzwzpcmw6a', - ], - ); - console.timeLog('DefaultTestContext', 'governanceDriver'); - +export const makeLiquidationTestKit = async ({ + swingsetTestKit, + agoricNamesRemotes, + walletFactoryDriver, + governanceDriver, + like, +}) => { const priceFeedDrivers = {} as Record< string, Awaited> @@ -130,21 +91,22 @@ export const makeLiquidationTestContext = async t => { const managerPath = `published.vaultFactory.managers.manager${managerIndex}`; const { advanceTimeBy, readLatest } = swingsetTestKit; - !priceFeedDrivers[collateralBrandKey] || - Fail`setup for ${collateralBrandKey} already ran`; - priceFeedDrivers[collateralBrandKey] = await makePriceFeedDriver( - collateralBrandKey, - agoricNamesRemotes, - walletFactoryDriver, - // TODO read from the config file - [ - 'agoric1krunjcqfrf7la48zrvdfeeqtls5r00ep68mzkr', - 'agoric19uscwxdac6cf6z7d5e26e0jm0lgwstc47cpll8', - 'agoric144rrhh4m09mh7aaffhm6xy223ym76gve2x7y78', - 'agoric19d6gnr9fyp6hev4tlrg87zjrzsd5gzr5qlfq2p', - 'agoric1n4fcxsnkxe4gj6e24naec99hzmc4pjfdccy5nj', - ], - ); + await 0; + if (!priceFeedDrivers[collateralBrandKey]) { + priceFeedDrivers[collateralBrandKey] = await makePriceFeedDriver( + collateralBrandKey, + agoricNamesRemotes, + walletFactoryDriver, + // TODO read from the config file + [ + 'agoric1krunjcqfrf7la48zrvdfeeqtls5r00ep68mzkr', + 'agoric19uscwxdac6cf6z7d5e26e0jm0lgwstc47cpll8', + 'agoric144rrhh4m09mh7aaffhm6xy223ym76gve2x7y78', + 'agoric19d6gnr9fyp6hev4tlrg87zjrzsd5gzr5qlfq2p', + 'agoric1n4fcxsnkxe4gj6e24naec99hzmc4pjfdccy5nj', + ], + ); + } // price feed logic treats zero time as "unset" so advance to nonzero await advanceTimeBy(1, 'seconds'); @@ -181,7 +143,7 @@ export const makeLiquidationTestContext = async t => { ); // confirm Relevant Governance Parameter Assumptions - t.like(readLatest(`${managerPath}.governance`), { + like(readLatest(`${managerPath}.governance`), { current: { DebtLimit: { value: { value: DebtLimitValue } }, InterestRate: { @@ -206,7 +168,7 @@ export const makeLiquidationTestContext = async t => { }, }, }); - t.like(readLatest('published.auction.governance'), { + like(readLatest('published.auction.governance'), { current: { AuctionStartDelay: { type: 'relativeTime', value: { relValue: 2n } }, ClockStep: { @@ -239,7 +201,7 @@ export const makeLiquidationTestContext = async t => { const notification = readLatest( `published.vaultFactory.managers.manager${managerIndex}.vaults.vault${vaultIndex}`, ); - t.like(notification, partial); + like(notification, partial); }, }; @@ -247,6 +209,7 @@ export const makeLiquidationTestContext = async t => { collateralBrandKey: string, managerIndex: number, setup: LiquidationSetup, + base: number = 0, ) => { await setupStartingState({ collateralBrandKey, @@ -258,14 +221,14 @@ export const makeLiquidationTestContext = async t => { await walletFactoryDriver.provideSmartWallet('agoric1minter'); for (let i = 0; i < setup.vaults.length; i += 1) { - const offerId = `open-${collateralBrandKey}-vault${i}`; + const offerId = `open-${collateralBrandKey}-vault${base + i}`; await minter.executeOfferMaker(Offers.vaults.OpenVault, { offerId, collateralBrandKey, wantMinted: setup.vaults[i].ist, giveCollateral: setup.vaults[i].atom, }); - t.like(minter.getLatestUpdateRecord(), { + like(minter.getLatestUpdateRecord(), { updated: 'offerStatus', status: { id: offerId, numWantsSatisfied: 1 }, }); @@ -313,7 +276,7 @@ export const makeLiquidationTestContext = async t => { ...setup.bids[i], maxBuy, }); - t.like( + like( swingsetTestKit.readLatest(`published.wallet.${buyerWalletAddress}`), { status: { @@ -327,18 +290,74 @@ export const makeLiquidationTestContext = async t => { }; return { - ...swingsetTestKit, - agoricNamesRemotes, check, - governanceDriver, priceFeedDrivers, - refreshAgoricNamesRemotes, - walletFactoryDriver, setupVaults, placeBids, }; }; +export const makeLiquidationTestContext = async t => { + const swingsetTestKit = await makeSwingsetTestKit(t.log, 'bundles/vaults'); + console.time('DefaultTestContext'); + + const { runUtils, storage } = swingsetTestKit; + console.timeLog('DefaultTestContext', 'swingsetTestKit'); + const { EV } = runUtils; + + // Wait for ATOM to make it into agoricNames + await EV.vat('bootstrap').consumeItem('vaultFactoryKit'); + console.timeLog('DefaultTestContext', 'vaultFactoryKit'); + + // has to be late enough for agoricNames data to have been published + const agoricNamesRemotes: AgoricNamesRemotes = + makeAgoricNamesRemotesFromFakeStorage(storage); + const refreshAgoricNamesRemotes = () => { + Object.assign( + agoricNamesRemotes, + makeAgoricNamesRemotesFromFakeStorage(storage), + ); + }; + agoricNamesRemotes.brand.ATOM || Fail`ATOM missing from agoricNames`; + console.timeLog('DefaultTestContext', 'agoricNamesRemotes'); + + const walletFactoryDriver = await makeWalletFactoryDriver( + runUtils, + storage, + agoricNamesRemotes, + ); + console.timeLog('DefaultTestContext', 'walletFactoryDriver'); + + const governanceDriver = await makeGovernanceDriver( + swingsetTestKit, + agoricNamesRemotes, + walletFactoryDriver, + // TODO read from the config file + [ + 'agoric1ldmtatp24qlllgxmrsjzcpe20fvlkp448zcuce', + 'agoric140dmkrz2e42ergjj7gyvejhzmjzurvqeq82ang', + 'agoric1w8wktaur4zf8qmmtn3n7x3r0jhsjkjntcm3u6h', + ], + ); + console.timeLog('DefaultTestContext', 'governanceDriver'); + + const liquidationTestKit = await makeLiquidationTestKit({ + swingsetTestKit, + agoricNamesRemotes, + walletFactoryDriver, + governanceDriver, + like: t.like, + }); + return { + ...swingsetTestKit, + ...liquidationTestKit, + agoricNamesRemotes, + refreshAgoricNamesRemotes, + walletFactoryDriver, + governanceDriver, + }; +}; + export type LiquidationTestContext = Awaited< ReturnType >; diff --git a/packages/vm-config/decentral-itest-vaults-config.json b/packages/vm-config/decentral-itest-vaults-config.json index a6664b50cd8..c2b63b11159 100644 --- a/packages/vm-config/decentral-itest-vaults-config.json +++ b/packages/vm-config/decentral-itest-vaults-config.json @@ -104,6 +104,34 @@ } ] }, + { + "module": "@agoric/builders/scripts/inter-protocol/add-collateral-core.js", + "entrypoint": "psmProposalBuilder", + "args": [ + { + "anchorOptions": { + "denom": "ibc/3914BDEF46F429A26917E4D8D434620EC4817DC6B6E68FB327E190902F1E9242", + "decimalPlaces": 18, + "keyword": "DAI_axl", + "proposedName": "DAI" + } + } + ] + }, + { + "module": "@agoric/builders/scripts/inter-protocol/add-collateral-core.js", + "entrypoint": "psmProposalBuilder", + "args": [ + { + "anchorOptions": { + "denom": "ibc/3D5291C23D776C3AA7A7ABB34C7B023193ECD2BC42EA19D3165B2CF9652117E7", + "decimalPlaces": 18, + "keyword": "DAI_grv", + "proposedName": "DAI" + } + } + ] + }, { "module": "@agoric/builders/scripts/inter-protocol/price-feed-core.js", "entrypoint": "defaultProposalBuilder", @@ -112,7 +140,7 @@ "contractTerms": { "POLL_INTERVAL": 30, "maxSubmissionCount": 1000, - "minSubmissionCount": 1, + "minSubmissionCount": 3, "restartDelay": 1, "timeout": 10, "minSubmissionValue": 1, @@ -120,8 +148,11 @@ }, "AGORIC_INSTANCE_NAME": "ATOM-USD price feed", "oracleAddresses": [ - "agoric1ldmtatp24qlllgxmrsjzcpe20fvlkp448zcuce", - "agoric140dmkrz2e42ergjj7gyvejhzmjzurvqeq82ang" + "agoric1krunjcqfrf7la48zrvdfeeqtls5r00ep68mzkr", + "agoric19uscwxdac6cf6z7d5e26e0jm0lgwstc47cpll8", + "agoric144rrhh4m09mh7aaffhm6xy223ym76gve2x7y78", + "agoric19d6gnr9fyp6hev4tlrg87zjrzsd5gzr5qlfq2p", + "agoric1n4fcxsnkxe4gj6e24naec99hzmc4pjfdccy5nj" ], "IN_BRAND_LOOKUP": [ "agoricNames", From 8f806bfdccddc067dfd848ea649230913b75c6be Mon Sep 17 00:00:00 2001 From: Chip Morningstar Date: Tue, 5 Dec 2023 12:55:05 -0800 Subject: [PATCH 2/3] chore: updates based on review comments --- packages/benchmark/src/benchmarkerator.js | 5 ++++- packages/boot/tools/liquidation.ts | 22 ++++++++++++++-------- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/packages/benchmark/src/benchmarkerator.js b/packages/benchmark/src/benchmarkerator.js index d648ccbe5ff..88f5f23e091 100644 --- a/packages/benchmark/src/benchmarkerator.js +++ b/packages/benchmark/src/benchmarkerator.js @@ -440,6 +440,8 @@ const printBenchmarkStats = stats => { `${stats.rounds} rounds in ${stats.elapsedTime}ns (${stats.timePerRound.toFixed(3)}/round})`, ); + // There are lots of temp variables used here simply so things lay out cleanly + // in the source text. Don't try to read too much meaning into the names themselves. const wc1 = 32; const hc1 = `${'Counter'.padEnd(wc1)}`; const dc1 = `${''.padEnd(wc1, '-')}`; @@ -592,7 +594,8 @@ export const makeBenchmarkerator = async () => { agoricNamesRemotes, walletFactoryDriver, governanceDriver, - like: () => {}, + // @ts-expect-error missing 'skip' property of real Ava like + t: { like: () => {} }, // XXX noop }); const actors = { diff --git a/packages/boot/tools/liquidation.ts b/packages/boot/tools/liquidation.ts index 117753dd5db..43320634475 100644 --- a/packages/boot/tools/liquidation.ts +++ b/packages/boot/tools/liquidation.ts @@ -68,7 +68,13 @@ export const makeLiquidationTestKit = async ({ agoricNamesRemotes, walletFactoryDriver, governanceDriver, - like, + t, +}: { + swingsetTestKit: SwingsetTestKit; + agoricNamesRemotes: AgoricNamesRemotes; + walletFactoryDriver: WalletFactoryDriver; + governanceDriver: GovernanceDriver; + t: Pick; }) => { const priceFeedDrivers = {} as Record< string, @@ -91,7 +97,7 @@ export const makeLiquidationTestKit = async ({ const managerPath = `published.vaultFactory.managers.manager${managerIndex}`; const { advanceTimeBy, readLatest } = swingsetTestKit; - await 0; + await null; if (!priceFeedDrivers[collateralBrandKey]) { priceFeedDrivers[collateralBrandKey] = await makePriceFeedDriver( collateralBrandKey, @@ -143,7 +149,7 @@ export const makeLiquidationTestKit = async ({ ); // confirm Relevant Governance Parameter Assumptions - like(readLatest(`${managerPath}.governance`), { + t.like(readLatest(`${managerPath}.governance`), { current: { DebtLimit: { value: { value: DebtLimitValue } }, InterestRate: { @@ -168,7 +174,7 @@ export const makeLiquidationTestKit = async ({ }, }, }); - like(readLatest('published.auction.governance'), { + t.like(readLatest('published.auction.governance'), { current: { AuctionStartDelay: { type: 'relativeTime', value: { relValue: 2n } }, ClockStep: { @@ -201,7 +207,7 @@ export const makeLiquidationTestKit = async ({ const notification = readLatest( `published.vaultFactory.managers.manager${managerIndex}.vaults.vault${vaultIndex}`, ); - like(notification, partial); + t.like(notification, partial); }, }; @@ -228,7 +234,7 @@ export const makeLiquidationTestKit = async ({ wantMinted: setup.vaults[i].ist, giveCollateral: setup.vaults[i].atom, }); - like(minter.getLatestUpdateRecord(), { + t.like(minter.getLatestUpdateRecord(), { updated: 'offerStatus', status: { id: offerId, numWantsSatisfied: 1 }, }); @@ -276,7 +282,7 @@ export const makeLiquidationTestKit = async ({ ...setup.bids[i], maxBuy, }); - like( + t.like( swingsetTestKit.readLatest(`published.wallet.${buyerWalletAddress}`), { status: { @@ -346,7 +352,7 @@ export const makeLiquidationTestContext = async t => { agoricNamesRemotes, walletFactoryDriver, governanceDriver, - like: t.like, + t, }); return { ...swingsetTestKit, From 881bf9d0ecb3ec2d786bc645a091ad313cd6375e Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Tue, 5 Dec 2023 14:32:24 -0800 Subject: [PATCH 3/3] chore(types): boot tool driver types --- packages/boot/tools/drivers.ts | 6 ++++++ packages/boot/tools/liquidation.ts | 10 +++++----- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/packages/boot/tools/drivers.ts b/packages/boot/tools/drivers.ts index fe51d2b3769..d15e19a293b 100644 --- a/packages/boot/tools/drivers.ts +++ b/packages/boot/tools/drivers.ts @@ -197,6 +197,8 @@ export const makePriceFeedDriver = async ( }, }; }; +harden(makePriceFeedDriver); +export type PriceFeedDriver = Awaited>; export const makeGovernanceDriver = async ( testKit: SwingsetTestKit, @@ -318,6 +320,8 @@ export const makeGovernanceDriver = async ( ecMembers, }; }; +harden(makeGovernanceDriver); +export type GovernanceDriver = Awaited>; export const makeZoeDriver = async (testKit: SwingsetTestKit) => { const { EV } = testKit.runUtils; @@ -402,3 +406,5 @@ export const makeZoeDriver = async (testKit: SwingsetTestKit) => { }, }; }; +harden(makeZoeDriver); +export type ZoeDriver = Awaited>; diff --git a/packages/boot/tools/liquidation.ts b/packages/boot/tools/liquidation.ts index 43320634475..9f5c9932c91 100644 --- a/packages/boot/tools/liquidation.ts +++ b/packages/boot/tools/liquidation.ts @@ -9,8 +9,11 @@ import { } from '@agoric/vats/tools/board-utils.js'; import { Offers } from '@agoric/inter-protocol/src/clientSupport.js'; import type { ExecutionContext } from 'ava'; -import { makeSwingsetTestKit } from './supports.ts'; +import { type SwingsetTestKit, makeSwingsetTestKit } from './supports.ts'; import { + type GovernanceDriver, + type PriceFeedDriver, + type WalletFactoryDriver, makeGovernanceDriver, makePriceFeedDriver, makeWalletFactoryDriver, @@ -76,10 +79,7 @@ export const makeLiquidationTestKit = async ({ governanceDriver: GovernanceDriver; t: Pick; }) => { - const priceFeedDrivers = {} as Record< - string, - Awaited> - >; + const priceFeedDrivers = {} as Record; console.timeLog('DefaultTestContext', 'priceFeedDriver');