From f83e8c235b691810f5bf075ed56fae3f78cb9ccb Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Mon, 9 Sep 2024 13:59:39 -0500 Subject: [PATCH] test: liquidation works with new price feeds, vaults, auctions --- .../bootstrapTests/price-feed-replace.test.ts | 194 ++++++++++++++++++ 1 file changed, 194 insertions(+) create mode 100644 packages/boot/test/bootstrapTests/price-feed-replace.test.ts diff --git a/packages/boot/test/bootstrapTests/price-feed-replace.test.ts b/packages/boot/test/bootstrapTests/price-feed-replace.test.ts new file mode 100644 index 000000000000..53be53ec82ff --- /dev/null +++ b/packages/boot/test/bootstrapTests/price-feed-replace.test.ts @@ -0,0 +1,194 @@ +/** + * @file The goal of this test is to see that the + * upgrade scripts re-wire all the contracts so new auctions and + * price feeds are connected to vaults correctly. + * + * 1. enter a bid + * 2. force prices to drop so a vault liquidates + * 3. verify that the bidder gets the liquidated assets. + */ +import { test as anyTest } from '@agoric/zoe/tools/prepare-test-env-ava.js'; + +import type { TestFn } from 'ava'; +import { ScheduleNotification } from '@agoric/inter-protocol/src/auction/scheduler.js'; +import { NonNullish } from '@agoric/internal'; +import { eventLoopIteration } from '@agoric/internal/src/testing-utils.js'; +import { + LiquidationTestContext, + likePayouts, + makeLiquidationTestContext, + scale6, + LiquidationSetup, + atomConfig, +} from '../../tools/liquidation.js'; + +const test = anyTest as TestFn; +test.before( + async t => + (t.context = await makeLiquidationTestContext(t, { env: process.env })), +); +test.after.always(t => t.context.shutdown()); + +const collateralBrandKey = 'ATOM'; +const managerIndex = 0; + +const setup: LiquidationSetup = { + vaults: [{ atom: 15, ist: 100, debt: 100.5 }], + bids: [{ give: '20IST', 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: 1.800828 } }], +}; + +test.serial('setupVaults; run replace-price-feeds proposals', async t => { + const { + agoricNamesRemotes, + buildProposal, + evalProposal, + priceFeedDrivers, + refreshAgoricNamesRemotes, + setupVaults, + } = t.context; + + await setupVaults(collateralBrandKey, managerIndex, setup); + + const instancePre = agoricNamesRemotes.instance['ATOM-USD price feed']; + + const perFeedBuilder = '@agoric/builders/scripts/vats/priceFeedSupport.js'; + t.log('building', perFeedBuilder); + const brandName = collateralBrandKey; + const opts = { + AGORIC_INSTANCE_NAME: `${brandName}-USD price feed`, + ORACLE_ADDRESSES: atomConfig.oracleAddresses, + IN_BRAND_LOOKUP: ['agoricNames', 'oracleBrand', brandName], + IN_BRAND_DECIMALS: 6, + OUT_BRAND_LOOKUP: ['agoricNames', 'oracleBrand', 'USD'], + OUT_BRAND_DECIMALS: 4, + }; + + t.log('building all relevant CoreEvals'); + const coreEvals = await Promise.all([ + buildProposal(perFeedBuilder, opts), + // '@agoric/builders/scripts/vats/upgradeScaledPriceAuthorities.js', + buildProposal('@agoric/builders/scripts/vats/upgradeVaults.js'), + buildProposal('@agoric/builders/scripts/vats/add-auction.js'), + ]); + const combined = { + evals: coreEvals.flatMap(e => e.evals), + bundles: coreEvals.flatMap(e => e.bundles), + }; + t.log('evaluating', coreEvals.length, 'scripts'); + await evalProposal(combined); + + refreshAgoricNamesRemotes(); + const instancePost = agoricNamesRemotes.instance['ATOM-USD price feed']; + t.not(instancePre, instancePost); + + await priceFeedDrivers[collateralBrandKey].refreshInvitations(); +}); + +test.serial('1. place bid', async t => { + const { placeBids, readLatest } = t.context; + await placeBids(collateralBrandKey, 'agoric1buyer', setup, 0); + + t.like(readLatest('published.wallet.agoric1buyer.current'), { + liveOffers: [['ATOM-bid1', { id: 'ATOM-bid1' }]], + }); +}); + +test.serial('2. trigger liquidation by changing price', async t => { + const { priceFeedDrivers, readLatest } = t.context; + + await priceFeedDrivers[collateralBrandKey].setPrice(9.99); + + t.log(readLatest('published.priceFeed.ATOM-USD_price_feed'), { + // aka 9.99 + amountIn: { value: 1000000n }, + amountOut: { value: 9990000n }, + }); + + // check nothing liquidating yet + const liveSchedule: ScheduleNotification = readLatest( + 'published.auction.schedule', + ); + t.is(liveSchedule.activeStartTime, null); + const metricsPath = `published.vaultFactory.managers.manager${managerIndex}.metrics`; + + t.like(readLatest(metricsPath), { + numActiveVaults: setup.vaults.length, + numLiquidatingVaults: 0, + }); +}); + +test.serial('3. verify liquidation', async t => { + const { advanceTimeBy, advanceTimeTo, readLatest } = t.context; + + const liveSchedule: ScheduleNotification = readLatest( + 'published.auction.schedule', + ); + const metricsPath = `published.vaultFactory.managers.manager${managerIndex}.metrics`; + + // advance time to start an auction + console.log(collateralBrandKey, 'step 1 of 10'); + await advanceTimeTo(NonNullish(liveSchedule.nextDescendingStepTime)); + await eventLoopIteration(); // let promises to update vstorage settle + + // vaultFactory sent collateral for liquidation + t.like(readLatest(metricsPath), { + numActiveVaults: 0, + numLiquidatingVaults: setup.vaults.length, + liquidatingCollateral: { + value: scale6(setup.auction.start.collateral), + }, + liquidatingDebt: { value: scale6(setup.auction.start.debt) }, + lockedQuote: null, + }); + + console.log(collateralBrandKey, 'step 2 of 10'); + await advanceTimeBy(3, 'minutes'); + t.like(readLatest(`published.auction.book${managerIndex}`), { + collateralAvailable: { value: scale6(setup.auction.start.collateral) }, + startCollateral: { value: scale6(setup.auction.start.collateral) }, + startProceedsGoal: { value: scale6(setup.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'); + + console.log(collateralBrandKey, 'step 5 of 10'); + await advanceTimeBy(3, 'minutes'); + + console.log(collateralBrandKey, 'step 6 of 10'); + await advanceTimeBy(3, 'minutes'); + t.like(readLatest(`published.auction.book${managerIndex}`), { + collateralAvailable: { value: 13199172n }, + }); + + 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'); + + t.like(readLatest('published.wallet.agoric1buyer'), { + status: { + id: `${collateralBrandKey}-bid1`, + payouts: likePayouts(outcome.bids[0].payouts), + }, + }); +});