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..88f5f23e091 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,66 @@ 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)}`; + // 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, '-')}`; + + 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 +506,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 +578,27 @@ 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, + // @ts-expect-error missing 'skip' property of real Ava like + t: { like: () => {} }, // XXX noop + }); + + const actors = { atom1: await walletFactoryDriver.provideSmartWallet( 'agoric1ldmtatp24qlllgxmrsjzcpe20fvlkp448zcuce', ), @@ -529,6 +609,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 +629,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 +651,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 +692,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 +728,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..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, @@ -315,8 +317,11 @@ export const makeGovernanceDriver = async ( await enactLatestProposal(); await testKit.advanceTimeBy(1, 'minutes'); }, + ecMembers, }; }; +harden(makeGovernanceDriver); +export type GovernanceDriver = Awaited>; export const makeZoeDriver = async (testKit: SwingsetTestKit) => { const { EV } = testKit.runUtils; @@ -401,3 +406,5 @@ export const makeZoeDriver = async (testKit: SwingsetTestKit) => { }, }; }; +harden(makeZoeDriver); +export type ZoeDriver = Awaited>; diff --git a/packages/boot/test/bootstrapTests/liquidation.ts b/packages/boot/tools/liquidation.ts similarity index 84% rename from packages/boot/test/bootstrapTests/liquidation.ts rename to packages/boot/tools/liquidation.ts index e7ec6ce39cc..9f5c9932c91 100644 --- a/packages/boot/test/bootstrapTests/liquidation.ts +++ b/packages/boot/tools/liquidation.ts @@ -9,12 +9,15 @@ 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 { type SwingsetTestKit, makeSwingsetTestKit } from './supports.ts'; import { + type GovernanceDriver, + type PriceFeedDriver, + type WalletFactoryDriver, makeGovernanceDriver, makePriceFeedDriver, makeWalletFactoryDriver, -} from '../../tools/drivers.ts'; +} from './drivers.ts'; export type LiquidationSetup = { vaults: { @@ -63,56 +66,20 @@ 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'); - - const priceFeedDrivers = {} as Record< - string, - Awaited> - >; +export const makeLiquidationTestKit = async ({ + swingsetTestKit, + agoricNamesRemotes, + walletFactoryDriver, + governanceDriver, + t, +}: { + swingsetTestKit: SwingsetTestKit; + agoricNamesRemotes: AgoricNamesRemotes; + walletFactoryDriver: WalletFactoryDriver; + governanceDriver: GovernanceDriver; + t: Pick; +}) => { + const priceFeedDrivers = {} as Record; console.timeLog('DefaultTestContext', 'priceFeedDriver'); @@ -130,21 +97,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 null; + 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'); @@ -247,6 +215,7 @@ export const makeLiquidationTestContext = async t => { collateralBrandKey: string, managerIndex: number, setup: LiquidationSetup, + base: number = 0, ) => { await setupStartingState({ collateralBrandKey, @@ -258,7 +227,7 @@ 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, @@ -327,18 +296,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, + t, + }); + 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",