From 680330c75705060b639ea3bd8a44191faa6ed3c0 Mon Sep 17 00:00:00 2001 From: JorgeLopes-BytePitch Date: Fri, 12 Jan 2024 11:16:22 +0000 Subject: [PATCH 01/39] create dedicated files for test assertions and tools --- .../test/liquidationVisibility/assertions.js | 157 +++++++++++ .../test/liquidationVisibility/tools.js | 243 ++++++++++++++++++ 2 files changed, 400 insertions(+) create mode 100644 packages/inter-protocol/test/liquidationVisibility/assertions.js create mode 100644 packages/inter-protocol/test/liquidationVisibility/tools.js diff --git a/packages/inter-protocol/test/liquidationVisibility/assertions.js b/packages/inter-protocol/test/liquidationVisibility/assertions.js new file mode 100644 index 00000000000..de449d0003f --- /dev/null +++ b/packages/inter-protocol/test/liquidationVisibility/assertions.js @@ -0,0 +1,157 @@ +import '@agoric/zoe/exported.js'; +import { E } from '@endo/eventual-send'; +import { assertPayoutAmount } from '@agoric/zoe/test/zoeTestHelpers.js'; +import { subscriptionTracker } from '../metrics.js'; +import { AmountMath } from '@agoric/ertp'; +import { + ceilMultiplyBy, + makeRatio, +} from '@agoric/zoe/src/contractSupport/index.js'; +import { subscribeEach } from '@agoric/notifier'; + +export const assertBidderPayout = async ( + t, + bidderSeat, + run, + curr, + aeth, + coll, +) => { + const bidderResult = await E(bidderSeat).getOfferResult(); + t.is(bidderResult, 'Your bid has been accepted'); + const payouts = await E(bidderSeat).getPayouts(); + const { Collateral: bidderCollateral, Bid: bidderBid } = payouts; + (!bidderBid && curr === 0n) || + (await assertPayoutAmount(t, run.issuer, bidderBid, run.make(curr))); + (!bidderCollateral && coll === 0n) || + (await assertPayoutAmount( + t, + aeth.issuer, + bidderCollateral, + aeth.make(coll), + 'amount ', + )); +}; + +export const assertReserveState = async (t, metricsTopic, method, expected) => { + const m = await subscriptionTracker(t, metricsTopic); + + switch (method) { + case 'initial': + await m.assertInitial(expected); + break; + case 'like': + await m.assertLike(expected); + break; + case 'state': + await m.assertState(expected); + break; + } +}; + +export const assertVaultCurrentDebt = async (t, vault, debt) => { + const debtAmount = await E(vault).getCurrentDebt(); + + if (debt === 0n) { + t.deepEqual(debtAmount.value, debt); + return; + } + + const fee = ceilMultiplyBy(debt, t.context.rates.mintFee); + + t.deepEqual( + debtAmount, + AmountMath.add(debt, fee), + 'borrower Minted amount does not match Vault current debt', + ); +}; + +export const assertVaultCollateral = async (t, vault, collateralValue) => { + const collateralAmount = await E(vault).getCollateralAmount(); + + t.deepEqual(collateralAmount, t.context.aeth.make(collateralValue)); +}; + +export const assertMintedAmount = async (t, vaultSeat, wantMinted) => { + const { Minted } = await E(vaultSeat).getFinalAllocation(); + + t.truthy(AmountMath.isEqual(Minted, wantMinted)); +}; + +export const assertVaultLocked = async (t, vaultNotifier, lockedValue) => { + const notification = await E(vaultNotifier).getUpdateSince(); + const lockedAmount = notification.value.locked; + + t.deepEqual(lockedAmount, t.context.aeth.make(lockedValue)); +}; + +export const assertVaultDebtSnapshot = async (t, vaultNotifier, wantMinted) => { + const notification = await E(vaultNotifier).getUpdateSince(); + const debtSnapshot = notification.value.debtSnapshot; + const fee = ceilMultiplyBy(wantMinted, t.context.rates.mintFee); + + t.deepEqual(debtSnapshot, { + debt: AmountMath.add(wantMinted, fee), + interest: makeRatio(100n, t.context.run.brand), + }); +}; + +export const assertVaultState = async (t, vaultNotifier, phase) => { + const notification = await E(vaultNotifier).getUpdateSince(); + const vaultState = notification.value.vaultState; + + t.is(vaultState, phase); +}; + +export const assertVaultSeatExited = async (t, vaultSeat) => { + t.truthy(await E(vaultSeat).hasExited()); +}; + +export const assertVaultFactoryRewardAllocation = async ( + t, + vaultFactory, + rewardValue, +) => { + const rewardAllocation = await E(vaultFactory).getRewardAllocation(); + + t.deepEqual(rewardAllocation, { + Minted: t.context.run.make(rewardValue), + }); +}; + +export const assertCollateralProceeds = async ( + t, + proceedsCollateralPayment, + collProceedsValue, +) => { + const collProceeds = await t.context.aeth.issuer.getAmountOf( + proceedsCollateralPayment, + ); + + t.deepEqual(collProceeds, t.context.aeth.make(collProceedsValue)); +}; + +// Update these assertions to use a tracker similar to test-auctionContract +export const assertBookData = async ( + t, + auctioneerBookDataSubscriber, + expectedBookData, +) => { + const auctioneerBookData = await E( + auctioneerBookDataSubscriber, + ).getUpdateSince(); + + t.deepEqual(auctioneerBookData.value, expectedBookData); +}; + +export const assertAuctioneerSchedule = async ( + t, + auctioneerPublicTopics, + expectedSchedule, +) => { + const auctioneerSchedule = await E( + auctioneerPublicTopics.schedule.subscriber, + ).getUpdateSince(); + + t.deepEqual(auctioneerSchedule.value, expectedSchedule); +}; \ No newline at end of file diff --git a/packages/inter-protocol/test/liquidationVisibility/tools.js b/packages/inter-protocol/test/liquidationVisibility/tools.js new file mode 100644 index 00000000000..5332d085317 --- /dev/null +++ b/packages/inter-protocol/test/liquidationVisibility/tools.js @@ -0,0 +1,243 @@ +import { E } from '@endo/eventual-send'; +import { withAmountUtils } from '../supports.js'; +import { makeIssuerKit } from '@agoric/ertp'; +import { unsafeMakeBundleCache } from '@agoric/swingset-vat/tools/bundleTool.js'; +import { allValues, objectMap } from '@agoric/internal'; +import { + getRunFromFaucet, + setupElectorateReserveAndAuction, +} from '../vaultFactory/vaultFactoryUtils.js'; +import { buildManualTimer } from '@agoric/swingset-vat/tools/manual-timer.js'; +import { startVaultFactory } from '../../src/proposals/econ-behaviors.js'; +import { makeRatioFromAmounts } from '@agoric/zoe/src/contractSupport/index.js'; +import { eventLoopIteration } from '@agoric/internal/src/testing-utils.js'; +import { TimeMath } from '@agoric/time'; +import '../../src/vaultFactory/types.js'; + +const contractRoots = { + faucet: './test/vaultFactory/faucet.js', + VaultFactory: './src/vaultFactory/vaultFactory.js', + reserve: './src/reserve/assetReserve.js', + auctioneer: './src/auction/auctioneer.js', +}; + +export const setupBasics = async zoe => { + const stableIssuer = await E(zoe).getFeeIssuer(); + const stableBrand = await E(stableIssuer).getBrand(); + + // @ts-expect-error missing mint + const run = withAmountUtils({ issuer: stableIssuer, brand: stableBrand }); + const aeth = withAmountUtils( + makeIssuerKit('aEth', 'nat', { decimalPlaces: 6 }), + ); + + const bundleCache = await unsafeMakeBundleCache('./bundles/'); + const bundles = await allValues({ + faucet: bundleCache.load(contractRoots.faucet, 'faucet'), + VaultFactory: bundleCache.load(contractRoots.VaultFactory, 'VaultFactory'), + reserve: bundleCache.load(contractRoots.reserve, 'reserve'), + auctioneer: bundleCache.load(contractRoots.auctioneer, 'auction'), + }); + const installation = objectMap(bundles, bundle => E(zoe).install(bundle)); + + return { + run, + aeth, + bundleCache, + bundles, + installation, + }; +}; + +/** + * @typedef {Record & { + * aeth: IssuerKit & import('../supports.js').AmountUtils; + * run: IssuerKit & import('../supports.js').AmountUtils; + * bundleCache: Awaited>; + * rates: VaultManagerParamValues; + * interestTiming: InterestTiming; + * zoe: ZoeService; + * }} Context + */ + +/** + * NOTE: called separately by each test so zoe/priceAuthority don't interfere + * This helper function will economicCommittee, reserve and auctioneer. + * It will start the vaultFactory and open a new vault with the collateral + * provided in the context. + * The collateral value will be set by the priceAuthority with the ratio provided + * by priceOrList + * + * @param {import('ava').ExecutionContext} t + * @param {NatValue[] | Ratio} priceOrList + * @param {Amount | undefined} unitAmountIn + * @param {import('@agoric/time').TimerService} timer + * @param {RelativeTime} quoteInterval + * @param {Partial} [auctionParams] + */ +export const setupServices = async ( + t, + priceOrList, + unitAmountIn, + timer = buildManualTimer(), + quoteInterval = 1n, + auctionParams = {}, +) => { + const { + zoe, + run, + aeth, + interestTiming, + minInitialDebt, + referencedUi, + rates, + } = t.context; + + t.context.timer = timer; + + const { space, priceAuthorityAdmin, aethTestPriceAuthority } = await setupElectorateReserveAndAuction( + t, + // @ts-expect-error inconsistent types with withAmountUtils + run, + aeth, + priceOrList, + quoteInterval, + unitAmountIn, + auctionParams, + ); + + const { + consume, + installation: { produce: iProduce }, + } = space; + + iProduce.VaultFactory.resolve(t.context.installation.VaultFactory); + iProduce.liquidate.resolve(t.context.installation.liquidate); + + await startVaultFactory( + space, + { interestTiming, options: { referencedUi } }, + minInitialDebt, + ); + + const governorCreatorFacet = E.get( + consume.vaultFactoryKit, + ).governorCreatorFacet; + const vaultFactoryCreatorFacetP = E.get(consume.vaultFactoryKit).creatorFacet; + + const reserveCreatorFacet = E.get(consume.reserveKit).creatorFacet; + const reservePublicFacet = E.get(consume.reserveKit).publicFacet; + const reserveKit = { reserveCreatorFacet, reservePublicFacet }; + + const aethVaultManagerP = E(vaultFactoryCreatorFacetP).addVaultType( + aeth.issuer, + 'AEth', + rates, + ); + + /** @typedef {import('../../src/proposals/econ-behaviors.js').AuctioneerKit} AuctioneerKit */ + /** @typedef {import('@agoric/zoe/tools/manualPriceAuthority.js').ManualPriceAuthority} ManualPriceAuthority */ + /** @typedef {import('../../src/vaultFactory/vaultFactory.js').VaultFactoryContract} VFC */ + /** + * @type {[ + * any, + * VaultFactoryCreatorFacet, + * VFC['publicFacet'], + * VaultManager, + * AuctioneerKit, + * ManualPriceAuthority, + * CollateralManager, + * ]} + */ + const [ + governorInstance, + vaultFactory, // creator + vfPublic, + aethVaultManager, + auctioneerKit, + priceAuthority, + aethCollateralManager, + ] = await Promise.all([ + E(consume.agoricNames).lookup('instance', 'VaultFactoryGovernor'), + vaultFactoryCreatorFacetP, + E.get(consume.vaultFactoryKit).publicFacet, + aethVaultManagerP, + consume.auctioneerKit, + /** @type {Promise} */ (consume.priceAuthority), + E(aethVaultManagerP).getPublicFacet(), + ]); + + const { g, v } = { + g: { + governorInstance, + governorPublicFacet: E(zoe).getPublicFacet(governorInstance), + governorCreatorFacet, + }, + v: { + vaultFactory, + vfPublic, + aethVaultManager, + aethCollateralManager, + }, + }; + + await E(auctioneerKit.creatorFacet).addBrand(aeth.issuer, 'Aeth'); + + return { + zoe, + timer, + space, + governor: g, + vaultFactory: v, + runKit: { issuer: run.issuer, brand: run.brand }, + reserveKit, + auctioneerKit, + priceAuthorityAdmin, + aethTestPriceAuthority, + }; +}; + +export const setClockAndAdvanceNTimes = async ( + timer, + times, + start, + incr = 1n, +) => { + let currentTime = start; + // first time through is at START, then n TIMES more plus INCR + for (let i = 0; i <= times; i += 1) { + await timer.advanceTo(TimeMath.absValue(currentTime)); + await eventLoopIteration(); + currentTime = TimeMath.addAbsRel(currentTime, TimeMath.relValue(incr)); + } + return currentTime; +}; + +// Calculate the nominalStart time (when liquidations happen), and the priceLock +// time (when prices are locked). Advance the clock to the priceLock time, then +// to the nominal start time. return the nominal start time and the auction +// start time, so the caller can check on liquidations in process before +// advancing the clock. +export const startAuctionClock = async (auctioneerKit, manualTimer) => { + const schedule = await E(auctioneerKit.creatorFacet).getSchedule(); + const priceDelay = await E(auctioneerKit.publicFacet).getPriceLockPeriod(); + const { startTime, startDelay } = schedule.nextAuctionSchedule; + const nominalStart = TimeMath.subtractAbsRel(startTime, startDelay); + const priceLockTime = TimeMath.subtractAbsRel(nominalStart, priceDelay); + await manualTimer.advanceTo(TimeMath.absValue(priceLockTime)); + await eventLoopIteration(); + + await manualTimer.advanceTo(TimeMath.absValue(nominalStart)); + await eventLoopIteration(); + return { startTime, time: nominalStart }; +}; + +export const bid = async (t, zoe, auctioneerKit, aeth, bidAmount, desired) => { + const bidderSeat = await E(zoe).offer( + E(auctioneerKit.publicFacet).makeBidInvitation(aeth.brand), + harden({ give: { Bid: bidAmount } }), + harden({ Bid: getRunFromFaucet(t, bidAmount.value) }), + { maxBuy: desired, offerPrice: makeRatioFromAmounts(bidAmount, desired) }, + ); + return bidderSeat; +}; From a3f6cf0794f4c8e2d6b8f00c20d145104ad71576 Mon Sep 17 00:00:00 2001 From: JorgeLopes-BytePitch Date: Fri, 12 Jan 2024 11:16:48 +0000 Subject: [PATCH 02/39] Add new test file for liquidation visibility --- .../test-liquidationVisibility.js | 481 ++++++++++++++++++ 1 file changed, 481 insertions(+) create mode 100644 packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js diff --git a/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js b/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js new file mode 100644 index 00000000000..ad4fb7f55f5 --- /dev/null +++ b/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js @@ -0,0 +1,481 @@ +// @ts-nocheck +import { test } from '@agoric/zoe/tools/prepare-test-env-ava.js'; +import { E } from '@endo/eventual-send'; +import { setUpZoeForTest } from '@agoric/zoe/tools/setup-zoe.js'; +import { deeplyFulfilled } from '@endo/marshal'; +import { makeTracer } from '@agoric/internal'; +import { buildManualTimer } from '@agoric/swingset-vat/tools/manual-timer.js'; +import { + makeRatio, + makeRatioFromAmounts, +} from '@agoric/zoe/src/contractSupport/index.js'; +import { + defaultParamValues, + legacyOfferResult, +} from '../vaultFactory/vaultFactoryUtils.js'; +import { SECONDS_PER_HOUR as ONE_HOUR } from '../../src/proposals/econ-behaviors.js'; +import { documentStorageSchema } from '@agoric/governance/tools/storageDoc.js'; +import { reserveInitialState } from '../metrics.js'; +import { eventLoopIteration } from '@agoric/internal/src/testing-utils.js'; +import { + bid, + setClockAndAdvanceNTimes, + setupBasics, + setupServices, + startAuctionClock, +} from './tools.js'; +import { + assertBidderPayout, + assertCollateralProceeds, + assertMintedAmount, + assertReserveState, + assertVaultCollateral, + assertVaultCurrentDebt, + assertVaultDebtSnapshot, + assertVaultFactoryRewardAllocation, + assertVaultLocked, + assertVaultSeatExited, + assertVaultState, + assertBookData, + assertAuctioneerSchedule, +} from './assertions.js'; +import { AmountMath } from '@agoric/ertp'; +import { makeManagerDriver } from '../vaultFactory/driver.js'; + +const trace = makeTracer('TestLiquidationVisibility', false); + +// IST is set as RUN to be able to use ../supports.js methods + +test.before(async t => { + const { zoe, feeMintAccessP } = await setUpZoeForTest(); + const feeMintAccess = await feeMintAccessP; + + const { run, aeth, bundleCache, bundles, installation } = + await setupBasics(zoe); + + const contextPs = { + zoe, + feeMintAccess, + bundles, + installation, + electorateTerms: undefined, + interestTiming: { + chargingPeriod: 2n, + recordingPeriod: 6n, + }, + minInitialDebt: 50n, + referencedUi: undefined, + rates: defaultParamValues(run.brand), + }; + const frozenCtx = await deeplyFulfilled(harden(contextPs)); + + t.context = { + ...frozenCtx, + bundleCache, + aeth, + run, + }; + + trace(t, 'CONTEXT'); +}); + +test('test create new vault', async t => { + const { zoe, run, aeth } = t.context; + const manualTimer = buildManualTimer(); + + // describe the purpose of interestTiming + t.context.interestTiming = { + chargingPeriod: 2n, + recordingPeriod: 10n, + }; + + const services = await setupServices( + t, + makeRatio(50n, run.brand, 10n, aeth.brand), + aeth.make(400n), + manualTimer, + undefined, + { StartFrequency: ONE_HOUR }, + ); + + const { + vaultFactory: { aethCollateralManager }, + reserveKit: { reserveCreatorFacet, reservePublicFacet }, + } = services; + + const metricsTopic = await E.get(E(reservePublicFacet).getPublicTopics()) + .metrics; + + let expectedReserveState = reserveInitialState(run.makeEmpty()); + await assertReserveState(t, metricsTopic, 'initial', expectedReserveState); + + await E(reserveCreatorFacet).addIssuer(aeth.issuer, 'Aeth'); + + const collateralAmount = aeth.make(400n); + const wantMinted = run.make(1600n); + + const vaultSeat = await E(zoe).offer( + await E(aethCollateralManager).makeVaultInvitation(), + harden({ + give: { Collateral: collateralAmount }, + want: { Minted: wantMinted }, + }), + harden({ + Collateral: aeth.mint.mintPayment(collateralAmount), + }), + ); + + const { + vault, + publicNotifiers: { vault: vaultNotifier }, + } = await legacyOfferResult(vaultSeat); + + await assertVaultCurrentDebt(t, vault, wantMinted); + await assertVaultState(t, vaultNotifier, 'active'); + await assertVaultDebtSnapshot(t, vaultNotifier, wantMinted); + await assertMintedAmount(t, vaultSeat, wantMinted); + await assertVaultCollateral(t, vault, 400n); +}); + +test('test vault liquidation ', async t => { + const { zoe, run, aeth } = t.context; + const manualTimer = buildManualTimer(); + + // describe the purpose of interestTiming + t.context.interestTiming = { + chargingPeriod: 2n, + recordingPeriod: 10n, + }; + + const services = await setupServices( + t, + makeRatio(50n, run.brand, 10n, aeth.brand), + aeth.make(400n), + manualTimer, + undefined, + { StartFrequency: ONE_HOUR }, + ); + + const { + vaultFactory: { vaultFactory, aethCollateralManager }, + aethTestPriceAuthority, + reserveKit: { reserveCreatorFacet, reservePublicFacet }, + auctioneerKit, + } = services; + + const metricsTopic = await E.get(E(reservePublicFacet).getPublicTopics()) + .metrics; + + let expectedReserveState = reserveInitialState(run.makeEmpty()); + await assertReserveState(t, metricsTopic, 'initial', expectedReserveState); + + await E(reserveCreatorFacet).addIssuer(aeth.issuer, 'Aeth'); + + const collateralAmount = aeth.make(400n); + const wantMinted = run.make(1600n); + + const vaultSeat = await E(zoe).offer( + await E(aethCollateralManager).makeVaultInvitation(), + harden({ + give: { Collateral: collateralAmount }, + want: { Minted: wantMinted }, + }), + harden({ + Collateral: aeth.mint.mintPayment(collateralAmount), + }), + ); + + // A bidder places a bid + const bidAmount = run.make(2000n); + const desired = aeth.make(400n); + const bidderSeat = await bid(t, zoe, auctioneerKit, aeth, bidAmount, desired); + + const { + vault, + publicNotifiers: { vault: vaultNotifier }, + } = await legacyOfferResult(vaultSeat); + + await assertVaultCurrentDebt(t, vault, wantMinted); + await assertVaultState(t, vaultNotifier, 'active'); + await assertVaultDebtSnapshot(t, vaultNotifier, wantMinted); + await assertMintedAmount(t, vaultSeat, wantMinted); + await assertVaultCollateral(t, vault, 400n); + + // drop collateral price from 5:1 to 4:1 and liquidate vault + aethTestPriceAuthority.setPrice(makeRatio(40n, run.brand, 10n, aeth.brand)); + + await assertVaultState(t, vaultNotifier, 'active'); + + const { startTime, time } = await startAuctionClock( + auctioneerKit, + manualTimer, + ); + let currentTime = time; + + await assertVaultState(t, vaultNotifier, 'liquidating'); + await assertVaultCollateral(t, vault, 0n); + await assertVaultCurrentDebt(t, vault, wantMinted); + + currentTime = await setClockAndAdvanceNTimes(manualTimer, 2, startTime, 2n); + trace(`advanced time to `, currentTime); + + await assertVaultState(t, vaultNotifier, 'liquidated'); + await assertVaultSeatExited(t, vaultSeat); + await assertVaultLocked(t, vaultNotifier, 0n); + await assertVaultCurrentDebt(t, vault, 0n); + await assertVaultFactoryRewardAllocation(t, vaultFactory, 80n); + + const closeSeat = await E(zoe).offer(E(vault).makeCloseInvitation()); + await E(closeSeat).getOfferResult(); + + const closeProceeds = await E(closeSeat).getPayouts(); + + await assertCollateralProceeds(t, closeProceeds.Collateral, 0n); + await assertVaultCollateral(t, vault, 0n); + await assertBidderPayout(t, bidderSeat, run, 320n, aeth, 400n); + + expectedReserveState = { + allocations: { + Aeth: undefined, + Fee: undefined, + }, + }; + await assertReserveState(t, metricsTopic, 'like', expectedReserveState); +}); + +test('test liquidate vault with snapshot', async t => { + const { zoe, run, aeth } = t.context; + const manualTimer = buildManualTimer(); + + t.context.interestTiming = { + chargingPeriod: 2n, + recordingPeriod: 10n, + }; + + const services = await setupServices( + t, + makeRatio(50n, run.brand, 10n, aeth.brand), + aeth.make(400n), + manualTimer, + undefined, + { StartFrequency: ONE_HOUR }, + ); + + const { + vaultFactory: { aethCollateralManager }, + aethTestPriceAuthority, + reserveKit: { reserveCreatorFacet }, + auctioneerKit, + } = services; + + await E(reserveCreatorFacet).addIssuer(aeth.issuer, 'Aeth'); + + const collateralAmount = aeth.make(400n); + const wantMinted = run.make(1600n); + + const vaultSeat = await E(zoe).offer( + await E(aethCollateralManager).makeVaultInvitation(), + harden({ + give: { Collateral: collateralAmount }, + want: { Minted: wantMinted }, + }), + harden({ + Collateral: aeth.mint.mintPayment(collateralAmount), + }), + ); + + // A bidder places a bid + const bidAmount = run.make(2000n); + const desired = aeth.make(400n); + await bid(t, zoe, auctioneerKit, aeth, bidAmount, desired); + + const { vault } = await legacyOfferResult(vaultSeat); + + // drop collateral price from 5:1 to 4:1 and liquidate vault + aethTestPriceAuthority.setPrice(makeRatio(40n, run.brand, 10n, aeth.brand)); + + const { startTime } = await startAuctionClock(auctioneerKit, manualTimer); + + await setClockAndAdvanceNTimes(manualTimer, 2, startTime, 2n); + + const closeSeat = await E(zoe).offer(E(vault).makeCloseInvitation()); + await E(closeSeat).getOfferResult(); + + const storage = await services.space.consume.chainStorage; + const doc = { + node: 'auction', + owner: 'the auctioneer contract', + pattern: 'mockChainStorageRoot.auction', + replacement: 'published.auction', + }; + await documentStorageSchema(t, storage, doc); +}); + +test('test auctioneer schedule and book data', async t => { + const { zoe, run, aeth, feeMintAccess } = t.context; + const manualTimer = buildManualTimer(); + const timerBrand = manualTimer.getTimerBrand(); + + t.context.interestTiming = { + chargingPeriod: 2n, + recordingPeriod: 10n, + }; + + const services = await setupServices( + t, + makeRatio(50n, run.brand, 10n, aeth.brand), + aeth.make(400n), + manualTimer, + undefined, + { StartFrequency: ONE_HOUR }, + ); + + const { + vaultFactory: { aethCollateralManager }, + aethTestPriceAuthority, + reserveKit: { reserveCreatorFacet }, + auctioneerKit, + } = services; + + await E(reserveCreatorFacet).addIssuer(aeth.issuer, 'Aeth'); + + const collateralAmount = aeth.make(400n); + const wantMinted = run.make(1600n); + + const vaultSeat = await E(zoe).offer( + await E(aethCollateralManager).makeVaultInvitation(), + harden({ + give: { Collateral: collateralAmount }, + want: { Minted: wantMinted }, + }), + harden({ + Collateral: aeth.mint.mintPayment(collateralAmount), + }), + ); + + // A bidder places a bid + const bidAmount = run.make(2000n); + const desired = aeth.make(400n); + await bid(t, zoe, auctioneerKit, aeth, bidAmount, desired); + + const { vault } = await legacyOfferResult(vaultSeat); + + // drop collateral price from 5:1 to 4:1 and liquidate vault + aethTestPriceAuthority.setPrice(makeRatio(40n, run.brand, 10n, aeth.brand)); + + const { startTime } = await startAuctionClock(auctioneerKit, manualTimer); + + // Explore auctioneerKit + // Update the auctioneer assertions to use a tracker similar to test-auctionContract + const auctioneerPublicTopics = await E( + auctioneerKit.publicFacet, + ).getPublicTopics(); + + const bookDataSubscriber = await E( + auctioneerKit.publicFacet, + ).getBookDataUpdates(aeth.brand); + + await assertBookData(t, bookDataSubscriber, { + collateralAvailable: aeth.make(400n), + currentPriceLevel: null, + proceedsRaised: undefined, + remainingProceedsGoal: null, + startCollateral: aeth.make(400n), + startPrice: makeRatioFromAmounts(run.make(4000000n), aeth.make(1000000n)), + startProceedsGoal: run.make(1680n), + }); + + await assertAuctioneerSchedule(t, auctioneerPublicTopics, { + activeStartTime: { + absValue: 3610n, + timerBrand, + }, + nextDescendingStepTime: { + absValue: 3610n, + timerBrand, + }, + nextStartTime: { absValue: 7210n, timerBrand }, + }); + + await setClockAndAdvanceNTimes(manualTimer, 2, startTime, 2n); + + await assertBookData(t, bookDataSubscriber, { + collateralAvailable: aeth.makeEmpty(), + currentPriceLevel: null, + proceedsRaised: undefined, + remainingProceedsGoal: null, + startCollateral: aeth.makeEmpty(), + startPrice: null, + startProceedsGoal: null, + }); + + await assertAuctioneerSchedule(t, auctioneerPublicTopics, { + activeStartTime: null, + nextDescendingStepTime: { + absValue: 7210n, + timerBrand, + }, + nextStartTime: { absValue: 7210n, timerBrand }, + }); +}); + +test('test storage keys', async t => { + const { zoe, run, aeth, feeMintAccess } = t.context; + const manualTimer = buildManualTimer(); + + t.context.interestTiming = { + chargingPeriod: 2n, + recordingPeriod: 10n, + }; + + const services = await setupServices( + t, + makeRatio(50n, run.brand, 10n, aeth.brand), + aeth.make(400n), + manualTimer, + undefined, + { StartFrequency: ONE_HOUR }, + ); + + const auctioneerPublicFacet = (await services.space.consume.auctioneerKit) + .publicFacet; + + // NOTE: the verification executed bellow can be done by the assertTopicPathData function + // we the deference is that we need to pass the aeth.brand as an argument for the getPublicTopics + // otherwise it will return the scheduler subscriber instead of the bookData + const topic = await E(auctioneerPublicFacet) + .getPublicTopics(aeth.brand) + .then(topics => topics['bookData']); + + t.is( + await topic?.storagePath, + 'mockChainStorageRoot.auction.book0', + 'topic storagePath must match', + ); + + const headValue = async subscriber => { + await eventLoopIteration(); + const record = await E(subscriber).subscribeAfter(); + return record.head.value; + }; + + const latest = /** @type {Record} */ ( + await headValue(topic.subscriber) + ); + + console.log(Object.keys(latest)); + + const dataKeys = [ + 'collateralAvailable', + 'currentPriceLevel', + 'proceedsRaised', + 'remainingProceedsGoal', + 'startCollateral', + 'startPrice', + 'startProceedsGoal', + ]; + + if (dataKeys !== undefined) { + t.deepEqual(Object.keys(latest), dataKeys, 'keys in topic feed must match'); + } +}); From 9d7abd6545bd60bde25481f32ac523a40b628bb2 Mon Sep 17 00:00:00 2001 From: JorgeLopes-BytePitch Date: Fri, 12 Jan 2024 11:17:11 +0000 Subject: [PATCH 03/39] auctioneer snapshot --- .../test-liquidationVisibility.js.md | 108 ++++++++++++++++++ .../test-liquidationVisibility.js.snap | Bin 0 -> 1437 bytes 2 files changed, 108 insertions(+) create mode 100644 packages/inter-protocol/test/liquidationVisibility/snapshots/test-liquidationVisibility.js.md create mode 100644 packages/inter-protocol/test/liquidationVisibility/snapshots/test-liquidationVisibility.js.snap diff --git a/packages/inter-protocol/test/liquidationVisibility/snapshots/test-liquidationVisibility.js.md b/packages/inter-protocol/test/liquidationVisibility/snapshots/test-liquidationVisibility.js.md new file mode 100644 index 00000000000..e3fe48cdcdf --- /dev/null +++ b/packages/inter-protocol/test/liquidationVisibility/snapshots/test-liquidationVisibility.js.md @@ -0,0 +1,108 @@ +# Snapshot report for `test/liquidationVisibility/test-liquidationVisibility.js` + +The actual snapshot is saved in `test-liquidationVisibility.js.snap`. + +Generated by [AVA](https://avajs.dev). + +## test liquidate vault with snapshot + +> Under "published", the "auction" node is delegated to the auctioneer contract. +> The example below illustrates the schema of the data published there. +> +> See also board marshalling conventions (_to appear_). + + [ + [ + 'published.auction.book0', + { + collateralAvailable: { + brand: Object @Alleged: aEth brand {}, + value: 0n, + }, + currentPriceLevel: null, + proceedsRaised: undefined, + remainingProceedsGoal: null, + startCollateral: { + brand: Object @Alleged: aEth brand {}, + value: 0n, + }, + startPrice: null, + startProceedsGoal: null, + }, + ], + [ + 'published.auction.governance', + { + current: { + AuctionStartDelay: { + type: 'relativeTime', + value: { + relValue: 10n, + timerBrand: Object @Alleged: timerBrand {}, + }, + }, + ClockStep: { + type: 'relativeTime', + value: { + relValue: 2n, + timerBrand: Object @Alleged: timerBrand {}, + }, + }, + DiscountStep: { + type: 'nat', + value: 2000n, + }, + Electorate: { + type: 'invitation', + value: { + brand: Object @Alleged: Zoe Invitation brand {}, + value: [ + { + description: 'questionPoser', + handle: Object @Alleged: InvitationHandle {}, + installation: Object @Alleged: BundleInstallation {}, + instance: Object @Alleged: InstanceHandle {}, + }, + ], + }, + }, + LowestRate: { + type: 'nat', + value: 5500n, + }, + PriceLockPeriod: { + type: 'relativeTime', + value: { + relValue: 3n, + timerBrand: Object @Alleged: timerBrand {}, + }, + }, + StartFrequency: { + type: 'relativeTime', + value: { + relValue: 3600n, + timerBrand: Object @Alleged: timerBrand {}, + }, + }, + StartingRate: { + type: 'nat', + value: 10500n, + }, + }, + }, + ], + [ + 'published.auction.schedule', + { + activeStartTime: null, + nextDescendingStepTime: { + absValue: 7210n, + timerBrand: Object @Alleged: timerBrand {}, + }, + nextStartTime: { + absValue: 7210n, + timerBrand: Object @Alleged: timerBrand {}, + }, + }, + ], + ] diff --git a/packages/inter-protocol/test/liquidationVisibility/snapshots/test-liquidationVisibility.js.snap b/packages/inter-protocol/test/liquidationVisibility/snapshots/test-liquidationVisibility.js.snap new file mode 100644 index 0000000000000000000000000000000000000000..cfe34852ff5d9fcbe84f49488d1abf5762f76a82 GIT binary patch literal 1437 zcmV;O1!DR^RzVV;jvtE%00000000BEmraZmMHI)o=6ieRW5?w~c3Ejw4}N7cETEB#Sy&Jd!^+|s zNetF>7ds8T-9vTH>~g?_kQircGj}%B|2Y&B@W}(B-D9Kexc7}6$hLG-4rBcy| zu937xh6y?r{AiGXs*29!Ew-gn8-- zAlp=|pkkSWK<1z}^uCI3>BmbN$!nxc$QNlX8quifISy)!Q)i}4ZHLyyP%2{7U8HKZ zIA?A!$8D1xVr}NgmUKzs%1!Q>j9LB>YWvKRuk$Ljp0g!tySBSH*P);DXwM;{yA_MB z;s=xmd!i5(XS!^baSaZs+5raiRFV^g{zfys{_>rn%I37wv9-K0oJEoS@unrsWw_NSO_btYWSayn+2L(fPgpYI_Ypz$C`?> zwr_eZH;7tajkW4~9gNxIC}G)lA>MY#TgG00q}%pMtgR9*ICMJ4xb0aIxDf->MEd zoo+GL3?m*Y#KMy8D8bNun6$p^6lmRFh$krX^z^oxbhIPpNDok;e0P}M`*U;rPHqbQ z#UZw$8OLFZ%-Utpz4+3}*_4%n|5;gD49-qLZx@u((i2s98OX=b4=P$ppuYq86WTbW zpp^vr43KA`6Dm5AK+gg>4}GYj4<^uSK(0f7sOY8yI;?A2722gM=y(Es5y;EXvWiY9 zXn*V0_(b$3tUD1~2NpmkIMZZN*>S8Nu~SC7-bFpOO67uFi)Zrcv1*dk;szx^+Mbg0HGxh6*#Rx6=vV^fKvtmlRCM#8k68a^h{X17hN;e0 z_*wBjc2v~~nU+3&S=|SSUyI2vB3^GP-P7bI``s?hXG{Mg_RRLDYGDcv-pP%!2^Cl0 zgo+7+2dZQuGC1Zj<3O}tV;>TntLG5GLaG0{G1_pjY_3kz3wNmF|m3<3O??T@~ zH^tksQX%US6_QaBG;HB;$nB2t0{&2nKW0a|4d! z`ILSX4Qz!jH63Qazqf4Ij??l3ar*M5nYgemQNueS{}NYcBkEkd;%rLS=NbGvzGu`u r$}MAwa=(GG;w&ZlYU7H4o_*ud Date: Fri, 12 Jan 2024 19:18:24 +0000 Subject: [PATCH 04/39] Update visibility tests and helper functions --- .../test/liquidationVisibility/assertions.js | 33 ++- .../test-liquidationVisibility.js | 197 +++++++----------- .../test/liquidationVisibility/tools.js | 52 +++-- 3 files changed, 140 insertions(+), 142 deletions(-) diff --git a/packages/inter-protocol/test/liquidationVisibility/assertions.js b/packages/inter-protocol/test/liquidationVisibility/assertions.js index de449d0003f..e0b2d4cb0e2 100644 --- a/packages/inter-protocol/test/liquidationVisibility/assertions.js +++ b/packages/inter-protocol/test/liquidationVisibility/assertions.js @@ -7,7 +7,7 @@ import { ceilMultiplyBy, makeRatio, } from '@agoric/zoe/src/contractSupport/index.js'; -import { subscribeEach } from '@agoric/notifier'; +import { headValue } from '../supports.js'; export const assertBidderPayout = async ( t, @@ -154,4 +154,33 @@ export const assertAuctioneerSchedule = async ( ).getUpdateSince(); t.deepEqual(auctioneerSchedule.value, expectedSchedule); -}; \ No newline at end of file +}; + +export const assertAuctioneerPathData = async ( + t, + hasTopics, + brand, + topicName, + path, + dataKeys, +) => { + let topic; + if (brand) { + topic = await E(hasTopics) + .getPublicTopics(brand) + .then(topics => topics[topicName]); + } else { + topic = await E(hasTopics) + .getPublicTopics() + .then(topics => topics[topicName]); + } + + t.is(await topic?.storagePath, path, 'topic storagePath must match'); + const latest = /** @type {Record} */ ( + await headValue(topic.subscriber) + ); + if (dataKeys !== undefined) { + // TODO consider making this a shape instead + t.deepEqual(Object.keys(latest), dataKeys, 'keys in topic feed must match'); + } +}; diff --git a/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js b/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js index ad4fb7f55f5..db12c832cba 100644 --- a/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js +++ b/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js @@ -1,10 +1,15 @@ // @ts-nocheck + import { test } from '@agoric/zoe/tools/prepare-test-env-ava.js'; import { E } from '@endo/eventual-send'; + import { setUpZoeForTest } from '@agoric/zoe/tools/setup-zoe.js'; import { deeplyFulfilled } from '@endo/marshal'; + import { makeTracer } from '@agoric/internal'; + import { buildManualTimer } from '@agoric/swingset-vat/tools/manual-timer.js'; + import { makeRatio, makeRatioFromAmounts, @@ -14,8 +19,10 @@ import { legacyOfferResult, } from '../vaultFactory/vaultFactoryUtils.js'; import { SECONDS_PER_HOUR as ONE_HOUR } from '../../src/proposals/econ-behaviors.js'; + import { documentStorageSchema } from '@agoric/governance/tools/storageDoc.js'; import { reserveInitialState } from '../metrics.js'; + import { eventLoopIteration } from '@agoric/internal/src/testing-utils.js'; import { bid, @@ -38,9 +45,8 @@ import { assertVaultState, assertBookData, assertAuctioneerSchedule, + assertAuctioneerPathData, } from './assertions.js'; -import { AmountMath } from '@agoric/ertp'; -import { makeManagerDriver } from '../vaultFactory/driver.js'; const trace = makeTracer('TestLiquidationVisibility', false); @@ -61,7 +67,7 @@ test.before(async t => { electorateTerms: undefined, interestTiming: { chargingPeriod: 2n, - recordingPeriod: 6n, + recordingPeriod: 10n, }, minInitialDebt: 50n, referencedUi: undefined, @@ -79,69 +85,12 @@ test.before(async t => { trace(t, 'CONTEXT'); }); -test('test create new vault', async t => { +test('test vault liquidation', async t => { const { zoe, run, aeth } = t.context; const manualTimer = buildManualTimer(); // describe the purpose of interestTiming - t.context.interestTiming = { - chargingPeriod: 2n, - recordingPeriod: 10n, - }; - - const services = await setupServices( - t, - makeRatio(50n, run.brand, 10n, aeth.brand), - aeth.make(400n), - manualTimer, - undefined, - { StartFrequency: ONE_HOUR }, - ); - - const { - vaultFactory: { aethCollateralManager }, - reserveKit: { reserveCreatorFacet, reservePublicFacet }, - } = services; - - const metricsTopic = await E.get(E(reservePublicFacet).getPublicTopics()) - .metrics; - - let expectedReserveState = reserveInitialState(run.makeEmpty()); - await assertReserveState(t, metricsTopic, 'initial', expectedReserveState); - - await E(reserveCreatorFacet).addIssuer(aeth.issuer, 'Aeth'); - - const collateralAmount = aeth.make(400n); - const wantMinted = run.make(1600n); - - const vaultSeat = await E(zoe).offer( - await E(aethCollateralManager).makeVaultInvitation(), - harden({ - give: { Collateral: collateralAmount }, - want: { Minted: wantMinted }, - }), - harden({ - Collateral: aeth.mint.mintPayment(collateralAmount), - }), - ); - - const { - vault, - publicNotifiers: { vault: vaultNotifier }, - } = await legacyOfferResult(vaultSeat); - - await assertVaultCurrentDebt(t, vault, wantMinted); - await assertVaultState(t, vaultNotifier, 'active'); - await assertVaultDebtSnapshot(t, vaultNotifier, wantMinted); - await assertMintedAmount(t, vaultSeat, wantMinted); - await assertVaultCollateral(t, vault, 400n); -}); -test('test vault liquidation ', async t => { - const { zoe, run, aeth } = t.context; - const manualTimer = buildManualTimer(); - - // describe the purpose of interestTiming t.context.interestTiming = { chargingPeriod: 2n, recordingPeriod: 10n, @@ -308,19 +257,36 @@ test('test liquidate vault with snapshot', async t => { pattern: 'mockChainStorageRoot.auction', replacement: 'published.auction', }; + await documentStorageSchema(t, storage, doc); }); -test('test auctioneer schedule and book data', async t => { - const { zoe, run, aeth, feeMintAccess } = t.context; +/* +The objective of this test is to be able to query the: +- Vault Manager +- Collateral at time of liquidation +- Debt at time of liquidation +- Time of liquidation +*/ + +test('test visibility of vault liquidation', async t => {}); + +/* +The objective of this test is to be able to query the: +- Auction identifier +- Auction start +- Collateral offered +- IST Target +- Collateral remaining +- Collateral sold +- IST target remaining +- IST raised +*/ +test('test visibility of auction', async t => { + const { zoe, run, aeth } = t.context; const manualTimer = buildManualTimer(); const timerBrand = manualTimer.getTimerBrand(); - t.context.interestTiming = { - chargingPeriod: 2n, - recordingPeriod: 10n, - }; - const services = await setupServices( t, makeRatio(50n, run.brand, 10n, aeth.brand), @@ -353,20 +319,19 @@ test('test auctioneer schedule and book data', async t => { }), ); - // A bidder places a bid const bidAmount = run.make(2000n); const desired = aeth.make(400n); await bid(t, zoe, auctioneerKit, aeth, bidAmount, desired); - const { vault } = await legacyOfferResult(vaultSeat); + const { + publicNotifiers: { vault: vaultNotifier }, + } = await legacyOfferResult(vaultSeat); - // drop collateral price from 5:1 to 4:1 and liquidate vault + // drop collateral price from 5:1 to 4:1 and start vault liquidating aethTestPriceAuthority.setPrice(makeRatio(40n, run.brand, 10n, aeth.brand)); - const { startTime } = await startAuctionClock(auctioneerKit, manualTimer); + await assertVaultState(t, vaultNotifier, 'liquidating'); - // Explore auctioneerKit - // Update the auctioneer assertions to use a tracker similar to test-auctionContract const auctioneerPublicTopics = await E( auctioneerKit.publicFacet, ).getPublicTopics(); @@ -398,6 +363,7 @@ test('test auctioneer schedule and book data', async t => { }); await setClockAndAdvanceNTimes(manualTimer, 2, startTime, 2n); + await assertVaultState(t, vaultNotifier, 'liquidated'); await assertBookData(t, bookDataSubscriber, { collateralAvailable: aeth.makeEmpty(), @@ -417,55 +383,8 @@ test('test auctioneer schedule and book data', async t => { }, nextStartTime: { absValue: 7210n, timerBrand }, }); -}); - -test('test storage keys', async t => { - const { zoe, run, aeth, feeMintAccess } = t.context; - const manualTimer = buildManualTimer(); - - t.context.interestTiming = { - chargingPeriod: 2n, - recordingPeriod: 10n, - }; - - const services = await setupServices( - t, - makeRatio(50n, run.brand, 10n, aeth.brand), - aeth.make(400n), - manualTimer, - undefined, - { StartFrequency: ONE_HOUR }, - ); - - const auctioneerPublicFacet = (await services.space.consume.auctioneerKit) - .publicFacet; - - // NOTE: the verification executed bellow can be done by the assertTopicPathData function - // we the deference is that we need to pass the aeth.brand as an argument for the getPublicTopics - // otherwise it will return the scheduler subscriber instead of the bookData - const topic = await E(auctioneerPublicFacet) - .getPublicTopics(aeth.brand) - .then(topics => topics['bookData']); - - t.is( - await topic?.storagePath, - 'mockChainStorageRoot.auction.book0', - 'topic storagePath must match', - ); - - const headValue = async subscriber => { - await eventLoopIteration(); - const record = await E(subscriber).subscribeAfter(); - return record.head.value; - }; - const latest = /** @type {Record} */ ( - await headValue(topic.subscriber) - ); - - console.log(Object.keys(latest)); - - const dataKeys = [ + const bookDataKeys = [ 'collateralAvailable', 'currentPriceLevel', 'proceedsRaised', @@ -474,8 +393,36 @@ test('test storage keys', async t => { 'startPrice', 'startProceedsGoal', ]; + await assertAuctioneerPathData( + t, + auctioneerKit.publicFacet, + aeth.brand, + 'bookData', + 'mockChainStorageRoot.auction.book0', + bookDataKeys, + ); - if (dataKeys !== undefined) { - t.deepEqual(Object.keys(latest), dataKeys, 'keys in topic feed must match'); - } + const scheduleDataKeys = [ + 'activeStartTime', + 'nextDescendingStepTime', + 'nextStartTime', + ]; + await assertAuctioneerPathData( + t, + auctioneerKit.publicFacet, + undefined, + 'schedule', + 'mockChainStorageRoot.auction.schedule', + scheduleDataKeys, + ); }); + +/* +The objective of this test is to be able to query the: +- Collateral sent to reserve +- Collateral returned to vault +- Debt returned to vault +- Shortfall sent to reserve +*/ + +test('test visibility of post auction distribution', async t => {}); diff --git a/packages/inter-protocol/test/liquidationVisibility/tools.js b/packages/inter-protocol/test/liquidationVisibility/tools.js index 5332d085317..8c8a706e42d 100644 --- a/packages/inter-protocol/test/liquidationVisibility/tools.js +++ b/packages/inter-protocol/test/liquidationVisibility/tools.js @@ -12,6 +12,8 @@ import { startVaultFactory } from '../../src/proposals/econ-behaviors.js'; import { makeRatioFromAmounts } from '@agoric/zoe/src/contractSupport/index.js'; import { eventLoopIteration } from '@agoric/internal/src/testing-utils.js'; import { TimeMath } from '@agoric/time'; +import { subscriptionTracker } from '../metrics.js'; +import { subscribeEach } from '@agoric/notifier'; import '../../src/vaultFactory/types.js'; const contractRoots = { @@ -62,11 +64,10 @@ export const setupBasics = async zoe => { /** * NOTE: called separately by each test so zoe/priceAuthority don't interfere - * This helper function will economicCommittee, reserve and auctioneer. - * It will start the vaultFactory and open a new vault with the collateral - * provided in the context. - * The collateral value will be set by the priceAuthority with the ratio provided - * by priceOrList + * This helper function will economicCommittee, reserve and auctioneer. It will + * start the vaultFactory and open a new vault with the collateral provided in + * the context. The collateral value will be set by the priceAuthority with the + * ratio provided by priceOrList * * @param {import('ava').ExecutionContext} t * @param {NatValue[] | Ratio} priceOrList @@ -95,16 +96,17 @@ export const setupServices = async ( t.context.timer = timer; - const { space, priceAuthorityAdmin, aethTestPriceAuthority } = await setupElectorateReserveAndAuction( - t, - // @ts-expect-error inconsistent types with withAmountUtils - run, - aeth, - priceOrList, - quoteInterval, - unitAmountIn, - auctionParams, - ); + const { space, priceAuthorityAdmin, aethTestPriceAuthority } = + await setupElectorateReserveAndAuction( + t, + // @ts-expect-error inconsistent types with withAmountUtils + run, + aeth, + priceOrList, + quoteInterval, + unitAmountIn, + auctionParams, + ); const { consume, @@ -241,3 +243,23 @@ export const bid = async (t, zoe, auctioneerKit, aeth, bidAmount, desired) => { ); return bidderSeat; }; + +export const getBookDataTracker = async (t, auctioneerPublicFacet, brand) => { + const tracker = E.when( + E(auctioneerPublicFacet).getBookDataUpdates(brand), + subscription => subscriptionTracker(t, subscribeEach(subscription)), + ); + + return tracker; +}; + +export const getSchedulerTracker = async (t, auctioneerPublicFacet) => { + const tracker = E.when( + E(auctioneerPublicFacet).getPublicTopics(), + subscription => + subscriptionTracker(t, subscribeEach(subscription.schedule.subscriber)), + ); + + return tracker; +}; + From f35bda13e1d69b53b79921458011220597bc79d8 Mon Sep 17 00:00:00 2001 From: JorgeLopes-BytePitch Date: Mon, 15 Jan 2024 12:33:43 +0000 Subject: [PATCH 05/39] add tools and assertions to match subscriber with vstorage data --- .../test/liquidationVisibility/assertions.js | 9 +++++++++ .../test/liquidationVisibility/tools.js | 15 +++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/packages/inter-protocol/test/liquidationVisibility/assertions.js b/packages/inter-protocol/test/liquidationVisibility/assertions.js index e0b2d4cb0e2..1e56b5adec4 100644 --- a/packages/inter-protocol/test/liquidationVisibility/assertions.js +++ b/packages/inter-protocol/test/liquidationVisibility/assertions.js @@ -184,3 +184,12 @@ export const assertAuctioneerPathData = async ( t.deepEqual(Object.keys(latest), dataKeys, 'keys in topic feed must match'); } }; + +export const assertVaultData = async ( + t, + vaultDataSubscriber, + vaultDataVstorage, +) => { + const auctioneerBookData = await E(vaultDataSubscriber).getUpdateSince(); + t.deepEqual(auctioneerBookData.value, vaultDataVstorage[0][1]); +}; diff --git a/packages/inter-protocol/test/liquidationVisibility/tools.js b/packages/inter-protocol/test/liquidationVisibility/tools.js index 8c8a706e42d..adcb5805415 100644 --- a/packages/inter-protocol/test/liquidationVisibility/tools.js +++ b/packages/inter-protocol/test/liquidationVisibility/tools.js @@ -263,3 +263,18 @@ export const getSchedulerTracker = async (t, auctioneerPublicFacet) => { return tracker; }; +export const getDataFromVstorage = async (storage, node) => { + const illustration = [...storage.keys()].sort().map( + /** @type {(k: string) => [string, unknown]} */ + key => [ + key.replace('mockChainStorageRoot.', 'published.'), + storage.getBody(key), + ], + ); + + const pruned = illustration.filter( + node ? ([key, _]) => key.startsWith(`published.${node}`) : _entry => true, + ); + + return pruned; +}; From 26dc28f585bc10e2815910b604baa4eb5bcbf088 Mon Sep 17 00:00:00 2001 From: JorgeLopes-BytePitch Date: Mon, 15 Jan 2024 12:34:18 +0000 Subject: [PATCH 06/39] update test visibility of vault liquidation --- .../test-liquidationVisibility.js | 79 +++++++++++++------ 1 file changed, 56 insertions(+), 23 deletions(-) diff --git a/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js b/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js index db12c832cba..3cc66bea997 100644 --- a/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js +++ b/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js @@ -2,14 +2,10 @@ import { test } from '@agoric/zoe/tools/prepare-test-env-ava.js'; import { E } from '@endo/eventual-send'; - import { setUpZoeForTest } from '@agoric/zoe/tools/setup-zoe.js'; import { deeplyFulfilled } from '@endo/marshal'; - import { makeTracer } from '@agoric/internal'; - import { buildManualTimer } from '@agoric/swingset-vat/tools/manual-timer.js'; - import { makeRatio, makeRatioFromAmounts, @@ -19,17 +15,15 @@ import { legacyOfferResult, } from '../vaultFactory/vaultFactoryUtils.js'; import { SECONDS_PER_HOUR as ONE_HOUR } from '../../src/proposals/econ-behaviors.js'; - import { documentStorageSchema } from '@agoric/governance/tools/storageDoc.js'; import { reserveInitialState } from '../metrics.js'; - -import { eventLoopIteration } from '@agoric/internal/src/testing-utils.js'; import { bid, setClockAndAdvanceNTimes, setupBasics, setupServices, startAuctionClock, + getDataFromVstorage, } from './tools.js'; import { assertBidderPayout, @@ -46,6 +40,7 @@ import { assertBookData, assertAuctioneerSchedule, assertAuctioneerPathData, + assertVaultData, } from './assertions.js'; const trace = makeTracer('TestLiquidationVisibility', false); @@ -89,13 +84,6 @@ test('test vault liquidation', async t => { const { zoe, run, aeth } = t.context; const manualTimer = buildManualTimer(); - // describe the purpose of interestTiming - - t.context.interestTiming = { - chargingPeriod: 2n, - recordingPeriod: 10n, - }; - const services = await setupServices( t, makeRatio(50n, run.brand, 10n, aeth.brand), @@ -196,11 +184,6 @@ test('test liquidate vault with snapshot', async t => { const { zoe, run, aeth } = t.context; const manualTimer = buildManualTimer(); - t.context.interestTiming = { - chargingPeriod: 2n, - recordingPeriod: 10n, - }; - const services = await setupServices( t, makeRatio(50n, run.brand, 10n, aeth.brand), @@ -262,17 +245,67 @@ test('test liquidate vault with snapshot', async t => { }); /* -The objective of this test is to be able to query the: +Verify the following data when extracted from vstorage: - Vault Manager - Collateral at time of liquidation - Debt at time of liquidation - Time of liquidation */ +test('test visibility of vault liquidation', async t => { + const { zoe, run, aeth } = t.context; + const manualTimer = buildManualTimer(); + + const services = await setupServices( + t, + makeRatio(50n, run.brand, 10n, aeth.brand), + aeth.make(400n), + manualTimer, + undefined, + { StartFrequency: ONE_HOUR }, + ); + + const { + vaultFactory: { aethCollateralManager }, + reserveKit: { reserveCreatorFacet, reservePublicFacet }, + auctioneerKit, + } = services; -test('test visibility of vault liquidation', async t => {}); + await E.get(E(reservePublicFacet).getPublicTopics()).metrics; + await E(reserveCreatorFacet).addIssuer(aeth.issuer, 'Aeth'); + + const collateralAmount = aeth.make(400n); + const wantMinted = run.make(1600n); + + const vaultSeat = await E(zoe).offer( + await E(aethCollateralManager).makeVaultInvitation(), + harden({ + give: { Collateral: collateralAmount }, + want: { Minted: wantMinted }, + }), + harden({ + Collateral: aeth.mint.mintPayment(collateralAmount), + }), + ); + + // A bidder places a bid + const bidAmount = run.make(2000n); + const desired = aeth.make(400n); + await bid(t, zoe, auctioneerKit, aeth, bidAmount, desired); + + // test is the vault data retrieved from publisher match the storage + const { + publicNotifiers: { vault: vaultNotifier }, + } = await legacyOfferResult(vaultSeat); + + const storage = await services.space.consume.chainStorage; + const node = 'vaultFactory.managers.manager0.vaults.vault0'; + const vaultDataVstorage = await getDataFromVstorage(storage, node); + + await assertVaultData(t, vaultNotifier, vaultDataVstorage); +}); /* -The objective of this test is to be able to query the: +Verify the following data when extracted from vstorage: - Auction identifier - Auction start - Collateral offered @@ -418,7 +451,7 @@ test('test visibility of auction', async t => { }); /* -The objective of this test is to be able to query the: +Verify the following data when extracted from vstorage: - Collateral sent to reserve - Collateral returned to vault - Debt returned to vault From ae88a64a1e1142c75342149d56a841ff40ccb10a Mon Sep 17 00:00:00 2001 From: anilhelvaci Date: Thu, 18 Jan 2024 12:20:06 +0300 Subject: [PATCH 07/39] fix(liquidationVisibility): fix linting errors --- .../test/liquidationVisibility/assertions.js | 5 ++++- .../test-liquidationVisibility.js | 6 ++++-- .../test/liquidationVisibility/tools.js | 16 +++++++--------- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/packages/inter-protocol/test/liquidationVisibility/assertions.js b/packages/inter-protocol/test/liquidationVisibility/assertions.js index 1e56b5adec4..91cc7995aca 100644 --- a/packages/inter-protocol/test/liquidationVisibility/assertions.js +++ b/packages/inter-protocol/test/liquidationVisibility/assertions.js @@ -1,13 +1,13 @@ import '@agoric/zoe/exported.js'; import { E } from '@endo/eventual-send'; import { assertPayoutAmount } from '@agoric/zoe/test/zoeTestHelpers.js'; -import { subscriptionTracker } from '../metrics.js'; import { AmountMath } from '@agoric/ertp'; import { ceilMultiplyBy, makeRatio, } from '@agoric/zoe/src/contractSupport/index.js'; import { headValue } from '../supports.js'; +import { subscriptionTracker } from '../metrics.js'; export const assertBidderPayout = async ( t, @@ -46,6 +46,9 @@ export const assertReserveState = async (t, metricsTopic, method, expected) => { case 'state': await m.assertState(expected); break; + default: + console.log('Default') + break; } }; diff --git a/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js b/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js index 3cc66bea997..af927e91ee9 100644 --- a/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js +++ b/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js @@ -10,12 +10,12 @@ import { makeRatio, makeRatioFromAmounts, } from '@agoric/zoe/src/contractSupport/index.js'; +import { documentStorageSchema } from '@agoric/governance/tools/storageDoc.js'; import { defaultParamValues, legacyOfferResult, } from '../vaultFactory/vaultFactoryUtils.js'; import { SECONDS_PER_HOUR as ONE_HOUR } from '../../src/proposals/econ-behaviors.js'; -import { documentStorageSchema } from '@agoric/governance/tools/storageDoc.js'; import { reserveInitialState } from '../metrics.js'; import { bid, @@ -458,4 +458,6 @@ Verify the following data when extracted from vstorage: - Shortfall sent to reserve */ -test('test visibility of post auction distribution', async t => {}); +test('test visibility of post auction distribution', async t => { + t.pass(); +}); diff --git a/packages/inter-protocol/test/liquidationVisibility/tools.js b/packages/inter-protocol/test/liquidationVisibility/tools.js index adcb5805415..1d3296450d7 100644 --- a/packages/inter-protocol/test/liquidationVisibility/tools.js +++ b/packages/inter-protocol/test/liquidationVisibility/tools.js @@ -1,20 +1,20 @@ import { E } from '@endo/eventual-send'; -import { withAmountUtils } from '../supports.js'; import { makeIssuerKit } from '@agoric/ertp'; import { unsafeMakeBundleCache } from '@agoric/swingset-vat/tools/bundleTool.js'; import { allValues, objectMap } from '@agoric/internal'; -import { - getRunFromFaucet, - setupElectorateReserveAndAuction, -} from '../vaultFactory/vaultFactoryUtils.js'; import { buildManualTimer } from '@agoric/swingset-vat/tools/manual-timer.js'; -import { startVaultFactory } from '../../src/proposals/econ-behaviors.js'; import { makeRatioFromAmounts } from '@agoric/zoe/src/contractSupport/index.js'; import { eventLoopIteration } from '@agoric/internal/src/testing-utils.js'; import { TimeMath } from '@agoric/time'; -import { subscriptionTracker } from '../metrics.js'; import { subscribeEach } from '@agoric/notifier'; import '../../src/vaultFactory/types.js'; +import { withAmountUtils } from '../supports.js'; +import { + getRunFromFaucet, + setupElectorateReserveAndAuction, +} from '../vaultFactory/vaultFactoryUtils.js'; +import { subscriptionTracker } from '../metrics.js'; +import { startVaultFactory } from '../../src/proposals/econ-behaviors.js'; const contractRoots = { faucet: './test/vaultFactory/faucet.js', @@ -157,7 +157,6 @@ export const setupServices = async ( vfPublic, aethVaultManager, auctioneerKit, - priceAuthority, aethCollateralManager, ] = await Promise.all([ E(consume.agoricNames).lookup('instance', 'VaultFactoryGovernor'), @@ -165,7 +164,6 @@ export const setupServices = async ( E.get(consume.vaultFactoryKit).publicFacet, aethVaultManagerP, consume.auctioneerKit, - /** @type {Promise} */ (consume.priceAuthority), E(aethVaultManagerP).getPublicFacet(), ]); From 7d477ff472733b5fa3e46f07dc01f7e7e519629d Mon Sep 17 00:00:00 2001 From: anilhelvaci Date: Thu, 18 Jan 2024 16:05:58 +0300 Subject: [PATCH 08/39] fix(liquidationVisibility): type error --- packages/inter-protocol/test/liquidationVisibility/tools.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/inter-protocol/test/liquidationVisibility/tools.js b/packages/inter-protocol/test/liquidationVisibility/tools.js index 1d3296450d7..860bc21c842 100644 --- a/packages/inter-protocol/test/liquidationVisibility/tools.js +++ b/packages/inter-protocol/test/liquidationVisibility/tools.js @@ -147,7 +147,6 @@ export const setupServices = async ( * VFC['publicFacet'], * VaultManager, * AuctioneerKit, - * ManualPriceAuthority, * CollateralManager, * ]} */ From b4c8a21a607249b8cc633f47652351869cf89442 Mon Sep 17 00:00:00 2001 From: JorgeLopes-BytePitch Date: Thu, 18 Jan 2024 17:17:02 +0000 Subject: [PATCH 09/39] feat(liquidationVisibility): create liquidation storageNodes and recorderKits BREAKING CHANGE: Introduced the `_timestamp` as an argument for the vaultManager liquidateVaults method. --- .../src/vaultFactory/vaultDirector.js | 3 +- .../src/vaultFactory/vaultManager.js | 71 +++++++++++++++++-- 2 files changed, 68 insertions(+), 6 deletions(-) diff --git a/packages/inter-protocol/src/vaultFactory/vaultDirector.js b/packages/inter-protocol/src/vaultFactory/vaultDirector.js index 1d3d0aeac30..2959315acf2 100644 --- a/packages/inter-protocol/src/vaultFactory/vaultDirector.js +++ b/packages/inter-protocol/src/vaultFactory/vaultDirector.js @@ -390,6 +390,7 @@ const prepareVaultDirector = ( const collateralUnit = await unitAmount(collateralBrand); + // @ts-ignore const kit = await makeVaultManagerKit({ debtMint, collateralBrand, @@ -423,7 +424,7 @@ const prepareVaultDirector = ( makeLiquidationWaker() { return makeWaker('liquidationWaker', _timestamp => { // XXX floating promise - allManagersDo(vm => vm.liquidateVaults(auctioneer)); + allManagersDo(vm => vm.liquidateVaults(auctioneer, _timestamp)); }); }, makeReschedulerWaker() { diff --git a/packages/inter-protocol/src/vaultFactory/vaultManager.js b/packages/inter-protocol/src/vaultFactory/vaultManager.js index 3b5413f331d..5186234384f 100644 --- a/packages/inter-protocol/src/vaultFactory/vaultManager.js +++ b/packages/inter-protocol/src/vaultFactory/vaultManager.js @@ -64,6 +64,7 @@ import { calculateMinimumCollateralization, minimumPrice } from './math.js'; import { makePrioritizedVaults } from './prioritizedVaults.js'; import { Phase, prepareVault } from './vault.js'; import { calculateDistributionPlan } from './proceeds.js'; +import { TimestampShape } from '@agoric/time'; const { details: X, Fail, quote: q } = assert; @@ -171,6 +172,7 @@ export const watchQuoteNotifier = async (notifierP, watcher, ...args) => { * @typedef {{ * assetTopicKit: import('@agoric/zoe/src/contractSupport/recorder.js').RecorderKit; * debtBrand: Brand<'nat'>; + * liquidationsStorageNode: StorageNode; * liquidatingVaults: SetStore; * metricsTopicKit: import('@agoric/zoe/src/contractSupport/recorder.js').RecorderKit; * poolIncrementSeat: ZCFSeat; @@ -227,7 +229,9 @@ export const prepareVaultManagerKit = ( const makeVault = prepareVault(baggage, makeRecorderKit, zcf); /** - * @param {HeldParams & { metricsStorageNode: StorageNode }} params + * @param {HeldParams & { metricsStorageNode: StorageNode } & { + * liquidationsStorageNode: StorageNode; + * }} params * @returns {HeldParams & ImmutableState & MutableState} */ const initState = params => { @@ -235,6 +239,7 @@ export const prepareVaultManagerKit = ( debtMint, collateralBrand, metricsStorageNode, + liquidationsStorageNode, startTimeStamp, storageNode, } = params; @@ -244,7 +249,7 @@ export const prepareVaultManagerKit = ( const immutable = { debtBrand, poolIncrementSeat: zcf.makeEmptySeatKit().zcfSeat, - + liquidationsStorageNode, /** * Vaults that have been sent for liquidation. When we get proceeds (or * lack thereof) back from the liquidator, we will allocate them among the @@ -336,7 +341,9 @@ export const prepareVaultManagerKit = ( getCollateralQuote: M.call().returns(PriceQuoteShape), getPublicFacet: M.call().returns(M.remotable('publicFacet')), lockOraclePrices: M.call().returns(PriceQuoteShape), - liquidateVaults: M.call(AuctionPFShape).returns(M.promise()), + liquidateVaults: M.call(AuctionPFShape, TimestampShape).returns( + M.promise(), + ), }), }, initState, @@ -815,6 +822,45 @@ export const prepareVaultManagerKit = ( // liqSeat should be empty at this point, except that funds are sent // asynchronously to the reserve. }, + + /** @param {{ absValue: BigInt }} timestamp */ + async liquidationRecorderKits(timestamp) { + const { + state: { liquidationsStorageNode }, + } = this; + + const timestampStorageNode = await E( + liquidationsStorageNode, + ).makeChildNode(`liquidation${timestamp.absValue}`); + + const [ + preAuctionStorageNode, + postAuctionStorageNode, + auctionResultStorageNode, + ] = await Promise.all([ + E(E(timestampStorageNode).makeChildNode('vaults')).makeChildNode( + 'preAuction', + ), + E(E(timestampStorageNode).makeChildNode('vaults')).makeChildNode( + 'postAuction', + ), + E(timestampStorageNode).makeChildNode('auctionResult'), + ]); + + const preAuctionRecorderKit = makeRecorderKit(preAuctionStorageNode); + const postAuctionRecorderKit = makeRecorderKit( + postAuctionStorageNode, + ); + const auctionResultRecorderKit = makeRecorderKit( + auctionResultStorageNode, + ); + + return { + preAuctionRecorderKit, + postAuctionRecorderKit, + auctionResultRecorderKit, + }; + }, }, manager: { @@ -1115,8 +1161,11 @@ export const prepareVaultManagerKit = ( void facets.helper.writeMetrics(); return storedCollateralQuote; }, - /** @param {ERef} auctionPF */ - async liquidateVaults(auctionPF) { + /** + * @param {ERef} auctionPF + * @param {{ absValue: BigInt }} timestamp + */ + async liquidateVaults(auctionPF, timestamp) { const { state, facets } = this; const { self, helper } = facets; const { @@ -1197,6 +1246,12 @@ export const prepareVaultManagerKit = ( ), ); + const { + preAuctionRecorderKit, + postAuctionRecorderKit, + auctionResultRecorderKit, + } = await helper.liquidationRecorderKits(timestamp); + // This is expected to wait for the duration of the auction, which // is controlled by the auction parameters startFrequency, clockStep, // and the difference between startingRate and lowestRate. @@ -1272,9 +1327,15 @@ export const prepareVaultManagerKit = ( const metricsStorageNode = await E( externalParams.storageNode, ).makeChildNode('metrics'); + + const liquidationsStorageNode = await E( + externalParams.storageNode, + ).makeChildNode('liquidations'); + return makeVaultManagerKitInternal({ ...externalParams, metricsStorageNode, + liquidationsStorageNode, }); }; return makeVaultManagerKit; From 21bb9ca37a76f30d010921f47540ea7ce4a081ab Mon Sep 17 00:00:00 2001 From: JorgeLopes-BytePitch Date: Fri, 19 Jan 2024 11:27:58 +0000 Subject: [PATCH 10/39] feat(liquidationVisibility): write preAuctionState and auctionResultState to Vstorage BREAKING CHANGE: a getVaultId method was included in the Vault interface, which returns the vault `idInManager` --- .../inter-protocol/src/vaultFactory/vault.js | 5 + .../src/vaultFactory/vaultDirector.js | 5 +- .../src/vaultFactory/vaultManager.js | 100 ++++++++++++++++-- 3 files changed, 102 insertions(+), 8 deletions(-) diff --git a/packages/inter-protocol/src/vaultFactory/vault.js b/packages/inter-protocol/src/vaultFactory/vault.js index 15fb0a1fc80..f0f0629d4bd 100644 --- a/packages/inter-protocol/src/vaultFactory/vault.js +++ b/packages/inter-protocol/src/vaultFactory/vault.js @@ -131,6 +131,7 @@ export const VaultI = M.interface('Vault', { getCurrentDebt: M.call().returns(AmountShape), getNormalizedDebt: M.call().returns(AmountShape), getVaultSeat: M.call().returns(SeatShape), + getVaultId: M.call().returns(M.any()), initVaultKit: M.call(SeatShape, StorageNodeShape).returns(M.promise()), liquidated: M.call().returns(undefined), liquidating: M.call().returns(undefined), @@ -597,6 +598,10 @@ export const prepareVault = (baggage, makeRecorderKit, zcf) => { return this.state.vaultSeat; }, + getVaultId() { + return this.state.idInManager; + }, + /** * @param {ZCFSeat} seat * @param {StorageNode} storageNode diff --git a/packages/inter-protocol/src/vaultFactory/vaultDirector.js b/packages/inter-protocol/src/vaultFactory/vaultDirector.js index 2959315acf2..cad6711197d 100644 --- a/packages/inter-protocol/src/vaultFactory/vaultDirector.js +++ b/packages/inter-protocol/src/vaultFactory/vaultDirector.js @@ -390,7 +390,10 @@ const prepareVaultDirector = ( const collateralUnit = await unitAmount(collateralBrand); - // @ts-ignore + /* FIXME(#5): after including the liquidationsStorageNode as an argument for makeVaultManagerKitInternal + * an error is triggered by missing that attribute + */ + // @ts-ignore const kit = await makeVaultManagerKit({ debtMint, collateralBrand, diff --git a/packages/inter-protocol/src/vaultFactory/vaultManager.js b/packages/inter-protocol/src/vaultFactory/vaultManager.js index 5186234384f..323970d3f70 100644 --- a/packages/inter-protocol/src/vaultFactory/vaultManager.js +++ b/packages/inter-protocol/src/vaultFactory/vaultManager.js @@ -207,6 +207,30 @@ export const watchQuoteNotifier = async (notifierP, watcher, ...args) => { * storedCollateralQuote: PriceQuote | null; * }} */ + +/** + * @typedef {{ + * vaultId: string; + * collateral: Amount<'nat'>; + * debt: Amount<'nat'>; + * }[]} PreAuctionState + * + * @typedef {{ + * vaultId: string; + * collateral: Amount<'nat'>; + * debt: Amount<'nat'>; + * phase: string; + * }[]} PostAuctionState + * + * @typedef {{ + * collateralForReserve: Amount<'nat'>; + * shortfallToReserve: Amount<'nat'>; + * mintedProceeds: Amount<'nat'>; + * collateralSold: Amount<'nat'>; + * collateralRemaining: Amount<'nat'>; + * endTime: Timestamp; + * }} AuctionResultState + */ // any b/c will be filled after start() const collateralEphemera = makeEphemeraProvider(() => /** @type {any} */ ({})); @@ -656,6 +680,33 @@ export const prepareVaultManagerKit = ( return E(metricsTopicKit.recorder).write(payload); }, + /** + * @param {{ + * preAuctionRecorderKit: any; + * postAuctionRecorderKit: any; + * auctionResultRecorderKit: any; + * }} liquidationRecorderKits + * @param {{ + * preAuctionState: PreAuctionState; + * postAuctionState: any; + * auctionResultState: any; + * }} liquidationPayloads + */ + writeLiquidations(liquidationRecorderKits, liquidationPayloads) { + const { + preAuctionRecorderKit, + postAuctionRecorderKit, + auctionResultRecorderKit, + } = liquidationRecorderKits; + + const { preAuctionState, postAuctionState, auctionResultState } = + liquidationPayloads; + + preAuctionRecorderKit.recorder.writeFinal(preAuctionState); + postAuctionRecorderKit.recorder.writeFinal(postAuctionState); + auctionResultRecorderKit.recorder.writeFinal(auctionResultState); + }, + /** * This is designed to tolerate an incomplete plan, in case * calculateDistributionPlan encounters an error during its calculation. @@ -1246,21 +1297,20 @@ export const prepareVaultManagerKit = ( ), ); - const { - preAuctionRecorderKit, - postAuctionRecorderKit, - auctionResultRecorderKit, - } = await helper.liquidationRecorderKits(timestamp); - // This is expected to wait for the duration of the auction, which // is controlled by the auction parameters startFrequency, clockStep, // and the difference between startingRate and lowestRate. - const [proceeds] = await Promise.all([deposited, userSeatPromise]); + const [liquidationRecorderKits, proceeds] = await Promise.all([ + helper.liquidationRecorderKits(timestamp), + deposited, + userSeatPromise, + ]); const { storedCollateralQuote } = collateralEphemera( this.state.collateralBrand, ); + let planAuctionResult; trace(`LiqV after long wait`, proceeds); try { const { plan, vaultsInPlan } = helper.planProceedsDistribution( @@ -1282,6 +1332,14 @@ export const prepareVaultManagerKit = ( totalDebt, vaultsInPlan, }); + + planAuctionResult = { + collateralForReserve: plan.collateralForReserve, + shortfallToReserve: plan.shortfallToReserve, + mintedProceeds: plan.mintedProceeds, + collateralSold: plan.collateralSold, + collateralRemaining: plan.collatRemaining, + }; } catch (err) { console.error('🚨 Error distributing proceeds:', err); } @@ -1294,6 +1352,34 @@ export const prepareVaultManagerKit = ( } void helper.writeMetrics(); + + /** @type {PreAuctionState} */ + let preAuctionState = []; + for (const [key, value] of vaultData.entries()) { + const vaultIdInManager = await E(key).getVaultId(); + const preAuctionVaultData = { + vaultId: vaultIdInManager, + collateral: value.collateralAmount, + debt: value.debtAmount, + }; + preAuctionState.push(preAuctionVaultData); + } + + const auctionEndTime = (await E(auctionPF).getSchedules()) + .nextAuctionSchedule?.endTime; + + // FIXME(5): the type of auctionEndTime {TimestampRecord | undefined} needs to match the type of endTime {Timestamp} + ///** @type {AuctionResultState} */ + const auctionResultState = { + ...planAuctionResult, + endTime: auctionEndTime, + }; + + void helper.writeLiquidations(liquidationRecorderKits, { + preAuctionState, + postAuctionState: [], + auctionResultState, + }); }, }, }, From 3739f2593df47033f939bc8053d26645db73a6cd Mon Sep 17 00:00:00 2001 From: JorgeLopes-BytePitch Date: Fri, 19 Jan 2024 16:23:38 +0000 Subject: [PATCH 11/39] fix(liquidationVisibility): fix type definitions errors and concurrently await multiple promises --- .../src/vaultFactory/vaultDirector.js | 4 -- .../src/vaultFactory/vaultManager.js | 56 +++++++++---------- 2 files changed, 26 insertions(+), 34 deletions(-) diff --git a/packages/inter-protocol/src/vaultFactory/vaultDirector.js b/packages/inter-protocol/src/vaultFactory/vaultDirector.js index cad6711197d..8281251adab 100644 --- a/packages/inter-protocol/src/vaultFactory/vaultDirector.js +++ b/packages/inter-protocol/src/vaultFactory/vaultDirector.js @@ -390,10 +390,6 @@ const prepareVaultDirector = ( const collateralUnit = await unitAmount(collateralBrand); - /* FIXME(#5): after including the liquidationsStorageNode as an argument for makeVaultManagerKitInternal - * an error is triggered by missing that attribute - */ - // @ts-ignore const kit = await makeVaultManagerKit({ debtMint, collateralBrand, diff --git a/packages/inter-protocol/src/vaultFactory/vaultManager.js b/packages/inter-protocol/src/vaultFactory/vaultManager.js index 323970d3f70..ce33dec9d60 100644 --- a/packages/inter-protocol/src/vaultFactory/vaultManager.js +++ b/packages/inter-protocol/src/vaultFactory/vaultManager.js @@ -223,12 +223,12 @@ export const watchQuoteNotifier = async (notifierP, watcher, ...args) => { * }[]} PostAuctionState * * @typedef {{ - * collateralForReserve: Amount<'nat'>; - * shortfallToReserve: Amount<'nat'>; - * mintedProceeds: Amount<'nat'>; - * collateralSold: Amount<'nat'>; - * collateralRemaining: Amount<'nat'>; - * endTime: Timestamp; + * collateralForReserve?: Amount<'nat'>; + * shortfallToReserve?: Amount<'nat'>; + * mintedProceeds?: Amount<'nat'>; + * collateralSold?: Amount<'nat'>; + * collateralRemaining?: Amount<'nat'>; + * endTime?: import('@agoric/time').TimestampRecord | null; * }} AuctionResultState */ // any b/c will be filled after start() @@ -253,7 +253,8 @@ export const prepareVaultManagerKit = ( const makeVault = prepareVault(baggage, makeRecorderKit, zcf); /** - * @param {HeldParams & { metricsStorageNode: StorageNode } & { + * @param {HeldParams & { + * metricsStorageNode: StorageNode; * liquidationsStorageNode: StorageNode; * }} params * @returns {HeldParams & ImmutableState & MutableState} @@ -880,9 +881,9 @@ export const prepareVaultManagerKit = ( state: { liquidationsStorageNode }, } = this; - const timestampStorageNode = await E( - liquidationsStorageNode, - ).makeChildNode(`liquidation${timestamp.absValue}`); + const timestampStorageNode = E(liquidationsStorageNode).makeChildNode( + `${timestamp.absValue}`, + ); const [ preAuctionStorageNode, @@ -1300,11 +1301,13 @@ export const prepareVaultManagerKit = ( // This is expected to wait for the duration of the auction, which // is controlled by the auction parameters startFrequency, clockStep, // and the difference between startingRate and lowestRate. - const [liquidationRecorderKits, proceeds] = await Promise.all([ - helper.liquidationRecorderKits(timestamp), - deposited, - userSeatPromise, - ]); + const [liquidationRecorderKits, auctionSchedule, proceeds] = + await Promise.all([ + helper.liquidationRecorderKits(timestamp), + E(auctionPF).getSchedules(), + deposited, + userSeatPromise, + ]); const { storedCollateralQuote } = collateralEphemera( this.state.collateralBrand, @@ -1358,21 +1361,17 @@ export const prepareVaultManagerKit = ( for (const [key, value] of vaultData.entries()) { const vaultIdInManager = await E(key).getVaultId(); const preAuctionVaultData = { - vaultId: vaultIdInManager, + vaultId: `vault${vaultIdInManager}`, collateral: value.collateralAmount, debt: value.debtAmount, }; preAuctionState.push(preAuctionVaultData); } - const auctionEndTime = (await E(auctionPF).getSchedules()) - .nextAuctionSchedule?.endTime; - - // FIXME(5): the type of auctionEndTime {TimestampRecord | undefined} needs to match the type of endTime {Timestamp} - ///** @type {AuctionResultState} */ + /** @type {AuctionResultState} */ const auctionResultState = { ...planAuctionResult, - endTime: auctionEndTime, + endTime: auctionSchedule.nextAuctionSchedule?.endTime, }; void helper.writeLiquidations(liquidationRecorderKits, { @@ -1406,17 +1405,14 @@ export const prepareVaultManagerKit = ( /** * @param {Omit< * Parameters[0], - * 'metricsStorageNode' + * 'metricsStorageNode' | 'liquidationsStorageNode' * >} externalParams */ const makeVaultManagerKit = async externalParams => { - const metricsStorageNode = await E( - externalParams.storageNode, - ).makeChildNode('metrics'); - - const liquidationsStorageNode = await E( - externalParams.storageNode, - ).makeChildNode('liquidations'); + const [metricsStorageNode, liquidationsStorageNode] = await Promise.all([ + E(externalParams.storageNode).makeChildNode('metrics'), + E(externalParams.storageNode).makeChildNode('liquidations'), + ]); return makeVaultManagerKitInternal({ ...externalParams, From 5dfa289c1fb4918e543b917a2ed8678c3ea98d3e Mon Sep 17 00:00:00 2001 From: anilhelvaci Date: Sun, 21 Jan 2024 16:29:41 +0300 Subject: [PATCH 12/39] chore(liquidationVisibility): #4 testing tools setup --- .../test/liquidationVisibility/assertions.js | 42 +++- .../test-liquidationVisibility.js | 207 +++++++++++++++++- .../test/liquidationVisibility/tools.js | 88 +++++++- 3 files changed, 323 insertions(+), 14 deletions(-) diff --git a/packages/inter-protocol/test/liquidationVisibility/assertions.js b/packages/inter-protocol/test/liquidationVisibility/assertions.js index 91cc7995aca..81ff3c7434e 100644 --- a/packages/inter-protocol/test/liquidationVisibility/assertions.js +++ b/packages/inter-protocol/test/liquidationVisibility/assertions.js @@ -47,9 +47,11 @@ export const assertReserveState = async (t, metricsTopic, method, expected) => { await m.assertState(expected); break; default: - console.log('Default') + console.log('Default'); break; } + + return m; }; export const assertVaultCurrentDebt = async (t, vault, debt) => { @@ -81,6 +83,20 @@ export const assertMintedAmount = async (t, vaultSeat, wantMinted) => { t.truthy(AmountMath.isEqual(Minted, wantMinted)); }; +export const assertMintedProceeds = async (t, vaultSeat, wantMinted) => { + const { Minted } = await E(vaultSeat).getFinalAllocation(); + const { Minted: proceedsMinted } = await E(vaultSeat).getPayouts(); + + t.truthy(AmountMath.isEqual(Minted, wantMinted)); + + t.truthy( + AmountMath.isEqual( + await E(t.context.run.issuer).getAmountOf(proceedsMinted), + wantMinted, + ), + ); +}; + export const assertVaultLocked = async (t, vaultNotifier, lockedValue) => { const notification = await E(vaultNotifier).getUpdateSince(); const lockedAmount = notification.value.locked; @@ -97,6 +113,8 @@ export const assertVaultDebtSnapshot = async (t, vaultNotifier, wantMinted) => { debt: AmountMath.add(wantMinted, fee), interest: makeRatio(100n, t.context.run.brand), }); + + return notification; }; export const assertVaultState = async (t, vaultNotifier, phase) => { @@ -104,6 +122,8 @@ export const assertVaultState = async (t, vaultNotifier, phase) => { const vaultState = notification.value.vaultState; t.is(vaultState, phase); + + return notification; }; export const assertVaultSeatExited = async (t, vaultSeat) => { @@ -122,16 +142,18 @@ export const assertVaultFactoryRewardAllocation = async ( }); }; -export const assertCollateralProceeds = async ( - t, - proceedsCollateralPayment, - collProceedsValue, -) => { - const collProceeds = await t.context.aeth.issuer.getAmountOf( - proceedsCollateralPayment, +export const assertCollateralProceeds = async (t, seat, colWanted) => { + const { Collateral: withdrawnCol } = await E(seat).getFinalAllocation(); + const proceeds4 = await E(seat).getPayouts(); + t.deepEqual(withdrawnCol, colWanted); + + const collateralWithdrawn = await proceeds4.Collateral; + t.truthy( + AmountMath.isEqual( + await E(t.context.aeth.issuer).getAmountOf(collateralWithdrawn), + colWanted, + ), ); - - t.deepEqual(collProceeds, t.context.aeth.make(collProceedsValue)); }; // Update these assertions to use a tracker similar to test-auctionContract diff --git a/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js b/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js index af927e91ee9..4152d36b06a 100644 --- a/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js +++ b/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js @@ -7,16 +7,26 @@ import { deeplyFulfilled } from '@endo/marshal'; import { makeTracer } from '@agoric/internal'; import { buildManualTimer } from '@agoric/swingset-vat/tools/manual-timer.js'; import { + ceilMultiplyBy, makeRatio, makeRatioFromAmounts, } from '@agoric/zoe/src/contractSupport/index.js'; import { documentStorageSchema } from '@agoric/governance/tools/storageDoc.js'; +import { AmountMath } from '@agoric/ertp'; import { defaultParamValues, legacyOfferResult, } from '../vaultFactory/vaultFactoryUtils.js'; -import { SECONDS_PER_HOUR as ONE_HOUR } from '../../src/proposals/econ-behaviors.js'; -import { reserveInitialState } from '../metrics.js'; +import { + SECONDS_PER_HOUR as ONE_HOUR, + SECONDS_PER_DAY as ONE_DAY, + SECONDS_PER_WEEK as ONE_WEEK, +} from '../../src/proposals/econ-behaviors.js'; +import { + reserveInitialState, + subscriptionTracker, + vaultManagerMetricsTracker, +} from '../metrics.js'; import { bid, setClockAndAdvanceNTimes, @@ -24,6 +34,7 @@ import { setupServices, startAuctionClock, getDataFromVstorage, + openVault, } from './tools.js'; import { assertBidderPayout, @@ -41,7 +52,9 @@ import { assertAuctioneerSchedule, assertAuctioneerPathData, assertVaultData, + assertMintedProceeds, } from './assertions.js'; +import { Phase } from '../vaultFactory/driver.js'; const trace = makeTracer('TestLiquidationVisibility', false); @@ -180,6 +193,196 @@ test('test vault liquidation', async t => { await assertReserveState(t, metricsTopic, 'like', expectedReserveState); }); +// We'll make a loan, and trigger liquidation via price changes. The interest +// rate is 40%. The liquidation margin is 105%. The priceAuthority will +// initially quote 10:1 Run:Aeth, and drop to 7:1. The loan will initially be +// overcollateralized 100%. Alice will withdraw enough of the overage that +// she'll get caught when prices drop. +// A bidder will buy at the 65% level, so there will be a shortfall. +test('Auction sells all collateral w/shortfall', async t => { + const { zoe, aeth, run, rates: defaultRates } = t.context; + + // Add a vaultManager with 10000 aeth collateral at a 200 aeth/Minted rate + const rates = harden({ + ...defaultRates, + // charge 40% interest / year + interestRate: run.makeRatio(40n), + liquidationMargin: run.makeRatio(130n), + }); + t.context.rates = rates; + + // Interest is charged daily, and auctions are every week + t.context.interestTiming = { + chargingPeriod: ONE_DAY, + recordingPeriod: ONE_DAY, + }; + + const manualTimer = buildManualTimer(); + const services = await setupServices( + t, + makeRatio(100n, run.brand, 10n, aeth.brand), + aeth.make(1n), + manualTimer, + ONE_WEEK, + { StartFrequency: ONE_HOUR }, + ); + + const { + vaultFactory: { aethCollateralManager }, + aethTestPriceAuthority, + reserveKit: { reserveCreatorFacet, reservePublicFacet }, + auctioneerKit, + } = services; + await E(reserveCreatorFacet).addIssuer(aeth.issuer, 'Aeth'); + + const metricsTopic = await E.get(E(reservePublicFacet).getPublicTopics()) + .metrics; + const m = await assertReserveState( + t, + metricsTopic, + 'initial', + reserveInitialState(run.makeEmpty()), + ); + let shortfallBalance = 0n; + + const aethVaultMetrics = await vaultManagerMetricsTracker( + t, + aethCollateralManager, + ); + await aethVaultMetrics.assertInitial({ + // present + numActiveVaults: 0, + numLiquidatingVaults: 0, + totalCollateral: aeth.make(0n), + totalDebt: run.make(0n), + retainedCollateral: aeth.make(0n), + + // running + numLiquidationsCompleted: 0, + numLiquidationsAborted: 0, + totalOverageReceived: run.make(0n), + totalProceedsReceived: run.make(0n), + totalCollateralSold: aeth.make(0n), + liquidatingCollateral: aeth.make(0n), + liquidatingDebt: run.make(0n), + totalShortfallReceived: run.make(0n), + lockedQuote: null, + }); + + // ALICE's loan //////////////////////////////////////////// + + // Create a loan for Alice for 5000 Minted with 1000 aeth collateral + // ratio is 4:1 + const aliceCollateralAmount = aeth.make(1000n); + const aliceWantMinted = run.make(5000n); + /** @type {UserSeat} */ + const aliceVaultSeat = await openVault({ + t, + cm: aethCollateralManager, + collateralAmount: aliceCollateralAmount, + wantMintedAmount: aliceWantMinted, + colKeyword: 'aeth', + }); + const { + vault: aliceVault, + publicNotifiers: { vault: aliceNotifier }, + } = await legacyOfferResult(aliceVaultSeat); + + await assertVaultCurrentDebt(t, aliceVault, aliceWantMinted); + await assertMintedProceeds(t, aliceVaultSeat, aliceWantMinted); + await assertVaultDebtSnapshot(t, aliceNotifier, aliceWantMinted); + + let totalDebt = 5250n; + await aethVaultMetrics.assertChange({ + numActiveVaults: 1, + totalCollateral: { value: 1000n }, + totalDebt: { value: totalDebt }, + }); + + // reduce collateral ///////////////////////////////////// + + trace(t, 'alice reduce collateral'); + + // Alice reduce collateral by 300. That leaves her at 700 * 10 > 1.05 * 5000. + // Prices will drop from 10 to 7, she'll be liquidated: 700 * 7 < 1.05 * 5000. + const collateralDecrement = aeth.make(300n); + const aliceReduceCollateralSeat = await E(zoe).offer( + E(aliceVault).makeAdjustBalancesInvitation(), + harden({ + want: { Collateral: collateralDecrement }, + }), + ); + await E(aliceReduceCollateralSeat).getOfferResult(); + + trace('alice '); + await assertCollateralProceeds(t, aliceReduceCollateralSeat, aeth.make(300n)); + + await assertVaultDebtSnapshot(t, aliceNotifier, aliceWantMinted); + trace(t, 'alice reduce collateral'); + await aethVaultMetrics.assertChange({ + totalCollateral: { value: 700n }, + }); + + await E(aethTestPriceAuthority).setPrice( + makeRatio(70n, run.brand, 10n, aeth.brand), + ); + trace(t, 'changed price to 7 RUN/Aeth'); + + // A BIDDER places a BID ////////////////////////// + const bidAmount = run.make(3300n); + const desired = aeth.make(700n); + const bidderSeat = await bid(t, zoe, auctioneerKit, aeth, bidAmount, desired); + + const { startTime: start1, time: now1 } = await startAuctionClock( + auctioneerKit, + manualTimer, + ); + let currentTime = now1; + + await aethVaultMetrics.assertChange({ + lockedQuote: makeRatioFromAmounts( + aeth.make(1_000_000n), + run.make(7_000_000n), + ), + }); + + // expect Alice to be liquidated because her collateral is too low. + await assertVaultState(t, aliceNotifier, Phase.LIQUIDATING); + + currentTime = await setClockAndAdvanceNTimes(manualTimer, 2, start1, 2n); + + await assertVaultState(t, aliceNotifier, Phase.LIQUIDATED); + trace(t, 'alice liquidated', currentTime); + totalDebt += 30n; + await aethVaultMetrics.assertChange({ + numActiveVaults: 0, + numLiquidatingVaults: 1, + liquidatingCollateral: { value: 700n }, + liquidatingDebt: { value: 5250n }, + lockedQuote: null, + }); + + shortfallBalance += 2065n; + await m.assertChange({ + shortfallBalance: { value: shortfallBalance }, + }); + + await aethVaultMetrics.assertChange({ + liquidatingDebt: { value: 0n }, + liquidatingCollateral: { value: 0n }, + totalCollateral: { value: 0n }, + totalDebt: { value: 0n }, + numLiquidatingVaults: 0, + numLiquidationsCompleted: 1, + totalCollateralSold: { value: 700n }, + totalProceedsReceived: { value: 3185n }, + totalShortfallReceived: { value: shortfallBalance }, + }); + + // Bidder bought 800 Aeth + await assertBidderPayout(t, bidderSeat, run, 115n, aeth, 700n); +}); + test('test liquidate vault with snapshot', async t => { const { zoe, run, aeth } = t.context; const manualTimer = buildManualTimer(); diff --git a/packages/inter-protocol/test/liquidationVisibility/tools.js b/packages/inter-protocol/test/liquidationVisibility/tools.js index 860bc21c842..905dd5ecdb8 100644 --- a/packages/inter-protocol/test/liquidationVisibility/tools.js +++ b/packages/inter-protocol/test/liquidationVisibility/tools.js @@ -1,7 +1,7 @@ import { E } from '@endo/eventual-send'; import { makeIssuerKit } from '@agoric/ertp'; import { unsafeMakeBundleCache } from '@agoric/swingset-vat/tools/bundleTool.js'; -import { allValues, objectMap } from '@agoric/internal'; +import { allValues, makeTracer, objectMap } from '@agoric/internal'; import { buildManualTimer } from '@agoric/swingset-vat/tools/manual-timer.js'; import { makeRatioFromAmounts } from '@agoric/zoe/src/contractSupport/index.js'; import { eventLoopIteration } from '@agoric/internal/src/testing-utils.js'; @@ -13,7 +13,7 @@ import { getRunFromFaucet, setupElectorateReserveAndAuction, } from '../vaultFactory/vaultFactoryUtils.js'; -import { subscriptionTracker } from '../metrics.js'; +import { subscriptionTracker, vaultManagerMetricsTracker } from '../metrics.js'; import { startVaultFactory } from '../../src/proposals/econ-behaviors.js'; const contractRoots = { @@ -23,6 +23,8 @@ const contractRoots = { auctioneer: './src/auction/auctioneer.js', }; +const trace = makeTracer('VisibilityTools', true); + export const setupBasics = async zoe => { const stableIssuer = await E(zoe).getFeeIssuer(); const stableBrand = await E(stableIssuer).getBrand(); @@ -147,6 +149,7 @@ export const setupServices = async ( * VFC['publicFacet'], * VaultManager, * AuctioneerKit, + * ManualPriceAuthority, * CollateralManager, * ]} */ @@ -156,6 +159,7 @@ export const setupServices = async ( vfPublic, aethVaultManager, auctioneerKit, + priceAuthority, aethCollateralManager, ] = await Promise.all([ E(consume.agoricNames).lookup('instance', 'VaultFactoryGovernor'), @@ -163,8 +167,15 @@ export const setupServices = async ( E.get(consume.vaultFactoryKit).publicFacet, aethVaultManagerP, consume.auctioneerKit, + /** @type {Promise} */ (consume.priceAuthority), E(aethVaultManagerP).getPublicFacet(), ]); + trace(t, 'pa', { + governorInstance, + vaultFactory, + vfPublic, + priceAuthority: !!priceAuthority, + }); const { g, v } = { g: { @@ -189,6 +200,7 @@ export const setupServices = async ( governor: g, vaultFactory: v, runKit: { issuer: run.issuer, brand: run.brand }, + priceAuthority, reserveKit, auctioneerKit, priceAuthorityAdmin, @@ -241,6 +253,78 @@ export const bid = async (t, zoe, auctioneerKit, aeth, bidAmount, desired) => { return bidderSeat; }; +/** + * @typedef {object} OpenVaultParams + * @property {any} t + * @property {CollateralManager} cm + * @property {Amount<'nat'>} collateralAmount + * @property {string} colKeyword + * @property {Amount<'nat'>} wantMintedAmount + */ + +/** + * @param {OpenVaultParams} params + * @returns {Promise>} + */ +export const openVault = async ({ + t, + cm, + collateralAmount, + colKeyword, + wantMintedAmount, +}) => { + return E(t.context.zoe).offer( + await E(cm).makeVaultInvitation(), + harden({ + give: { Collateral: collateralAmount }, + want: { Minted: wantMintedAmount }, + }), + harden({ + Collateral: t.context[colKeyword].mint.mintPayment(collateralAmount), + }), + ); +}; + +/** + * @typedef {object} GetTrackerParams + * @property {any} t + * @property {CollateralManager} collateralManager + * @property {AssetReservePublicFacet} reservePublicFacet + */ + +/** + * @typedef {object} Trackers + * @property {object | undefined} reserveTracker + * @property {object | undefined} collateralManagerTracker + */ + +/** + * @param {GetTrackerParams} getTrackerParams + * @returns {Promise} + */ +export const getMetricTrackers = async ({ + t, + collateralManager, + reservePublicFacet, +}) => { + /** @type {Trackers} */ + const trackers = harden({}); + if (reservePublicFacet) { + const metricsTopic = await E.get(E(reservePublicFacet).getPublicTopics()) + .metrics; + trackers.reserveTracker = await subscriptionTracker(t, metricsTopic); + } + + if (collateralManager) { + trackers.collateralManagerTracker = await vaultManagerMetricsTracker( + t, + collateralManager, + ); + } + + return trackers; +}; + export const getBookDataTracker = async (t, auctioneerPublicFacet, brand) => { const tracker = E.when( E(auctioneerPublicFacet).getBookDataUpdates(brand), From bbd2e487becc09a0f03244c7999a00529c8a5658 Mon Sep 17 00:00:00 2001 From: anilhelvaci Date: Mon, 22 Jan 2024 09:37:01 +0300 Subject: [PATCH 13/39] chore(liquidationVisibility): #4 improve testing tools --- .../test/liquidationVisibility/assertions.js | 12 ++-- .../test-liquidationVisibility.js | 44 +++++++-------- .../test/liquidationVisibility/tools.js | 55 +++++++++++++++++-- 3 files changed, 77 insertions(+), 34 deletions(-) diff --git a/packages/inter-protocol/test/liquidationVisibility/assertions.js b/packages/inter-protocol/test/liquidationVisibility/assertions.js index 81ff3c7434e..82a29889c55 100644 --- a/packages/inter-protocol/test/liquidationVisibility/assertions.js +++ b/packages/inter-protocol/test/liquidationVisibility/assertions.js @@ -33,25 +33,21 @@ export const assertBidderPayout = async ( )); }; -export const assertReserveState = async (t, metricsTopic, method, expected) => { - const m = await subscriptionTracker(t, metricsTopic); - +export const assertReserveState = async (metricTracker, method, expected) => { switch (method) { case 'initial': - await m.assertInitial(expected); + await metricTracker.assertInitial(expected); break; case 'like': - await m.assertLike(expected); + await metricTracker.assertLike(expected); break; case 'state': - await m.assertState(expected); + await metricTracker.assertState(expected); break; default: console.log('Default'); break; } - - return m; }; export const assertVaultCurrentDebt = async (t, vault, debt) => { diff --git a/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js b/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js index 4152d36b06a..720893167d8 100644 --- a/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js +++ b/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js @@ -35,7 +35,8 @@ import { startAuctionClock, getDataFromVstorage, openVault, -} from './tools.js'; + getMetricTrackers, adjustVault +} from "./tools.js"; import { assertBidderPayout, assertCollateralProceeds, @@ -235,21 +236,20 @@ test('Auction sells all collateral w/shortfall', async t => { } = services; await E(reserveCreatorFacet).addIssuer(aeth.issuer, 'Aeth'); - const metricsTopic = await E.get(E(reservePublicFacet).getPublicTopics()) - .metrics; - const m = await assertReserveState( + const { reserveTracker, collateralManagerTracker } = await getMetricTrackers({ t, - metricsTopic, + collateralManager: aethCollateralManager, + reservePublicFacet, + }); + + await assertReserveState( + reserveTracker, 'initial', reserveInitialState(run.makeEmpty()), ); let shortfallBalance = 0n; - const aethVaultMetrics = await vaultManagerMetricsTracker( - t, - aethCollateralManager, - ); - await aethVaultMetrics.assertInitial({ + await collateralManagerTracker.assertInitial({ // present numActiveVaults: 0, numLiquidatingVaults: 0, @@ -293,7 +293,7 @@ test('Auction sells all collateral w/shortfall', async t => { await assertVaultDebtSnapshot(t, aliceNotifier, aliceWantMinted); let totalDebt = 5250n; - await aethVaultMetrics.assertChange({ + await collateralManagerTracker.assertChange({ numActiveVaults: 1, totalCollateral: { value: 1000n }, totalDebt: { value: totalDebt }, @@ -306,12 +306,13 @@ test('Auction sells all collateral w/shortfall', async t => { // Alice reduce collateral by 300. That leaves her at 700 * 10 > 1.05 * 5000. // Prices will drop from 10 to 7, she'll be liquidated: 700 * 7 < 1.05 * 5000. const collateralDecrement = aeth.make(300n); - const aliceReduceCollateralSeat = await E(zoe).offer( - E(aliceVault).makeAdjustBalancesInvitation(), - harden({ + const aliceReduceCollateralSeat = await adjustVault({ + t, + vault: aliceVault, + proposal: { want: { Collateral: collateralDecrement }, - }), - ); + }, + }); await E(aliceReduceCollateralSeat).getOfferResult(); trace('alice '); @@ -319,7 +320,7 @@ test('Auction sells all collateral w/shortfall', async t => { await assertVaultDebtSnapshot(t, aliceNotifier, aliceWantMinted); trace(t, 'alice reduce collateral'); - await aethVaultMetrics.assertChange({ + await collateralManagerTracker.assertChange({ totalCollateral: { value: 700n }, }); @@ -339,7 +340,7 @@ test('Auction sells all collateral w/shortfall', async t => { ); let currentTime = now1; - await aethVaultMetrics.assertChange({ + await collateralManagerTracker.assertChange({ lockedQuote: makeRatioFromAmounts( aeth.make(1_000_000n), run.make(7_000_000n), @@ -353,8 +354,7 @@ test('Auction sells all collateral w/shortfall', async t => { await assertVaultState(t, aliceNotifier, Phase.LIQUIDATED); trace(t, 'alice liquidated', currentTime); - totalDebt += 30n; - await aethVaultMetrics.assertChange({ + await collateralManagerTracker.assertChange({ numActiveVaults: 0, numLiquidatingVaults: 1, liquidatingCollateral: { value: 700n }, @@ -363,11 +363,11 @@ test('Auction sells all collateral w/shortfall', async t => { }); shortfallBalance += 2065n; - await m.assertChange({ + await reserveTracker.assertChange({ shortfallBalance: { value: shortfallBalance }, }); - await aethVaultMetrics.assertChange({ + await collateralManagerTracker.assertChange({ liquidatingDebt: { value: 0n }, liquidatingCollateral: { value: 0n }, totalCollateral: { value: 0n }, diff --git a/packages/inter-protocol/test/liquidationVisibility/tools.js b/packages/inter-protocol/test/liquidationVisibility/tools.js index 905dd5ecdb8..0fed62186ad 100644 --- a/packages/inter-protocol/test/liquidationVisibility/tools.js +++ b/packages/inter-protocol/test/liquidationVisibility/tools.js @@ -8,6 +8,7 @@ import { eventLoopIteration } from '@agoric/internal/src/testing-utils.js'; import { TimeMath } from '@agoric/time'; import { subscribeEach } from '@agoric/notifier'; import '../../src/vaultFactory/types.js'; +import exp from 'constants'; import { withAmountUtils } from '../supports.js'; import { getRunFromFaucet, @@ -285,6 +286,52 @@ export const openVault = async ({ ); }; +/** + * @typedef {object} AdjustVaultParams + * @property {object} t + * @property {Vault} vault + * @property {{ + * want: [ + * { + * Collateral: Amount<'nat'>; + * Minted: Amount<'nat'>; + * }, + * ]; + * give: [ + * { + * Collateral: Amount<'nat'>; + * Minted: Amount<'nat'>; + * }, + * ]; + * }} proposal + * @property {{ + * want: [ + * { + * Collateral: Payment; + * Minted: Payment; + * }, + * ]; + * give: [ + * { + * Collateral: Payment; + * Minted: Payment; + * }, + * ]; + * }} [payment] + */ + +/** + * @param {AdjustVaultParams} adjustVaultParams + * @returns {Promise} + */ +export const adjustVault = async ({ t, vault, proposal, payment }) => { + return E(t.context.zoe).offer( + E(vault).makeAdjustBalancesInvitation(), + harden(proposal), + payment, + ); +}; + /** * @typedef {object} GetTrackerParams * @property {any} t @@ -294,8 +341,8 @@ export const openVault = async ({ /** * @typedef {object} Trackers - * @property {object | undefined} reserveTracker - * @property {object | undefined} collateralManagerTracker + * @property {object} [reserveTracker] + * @property {object} [collateralManagerTracker] */ /** @@ -308,7 +355,7 @@ export const getMetricTrackers = async ({ reservePublicFacet, }) => { /** @type {Trackers} */ - const trackers = harden({}); + const trackers = {}; if (reservePublicFacet) { const metricsTopic = await E.get(E(reservePublicFacet).getPublicTopics()) .metrics; @@ -322,7 +369,7 @@ export const getMetricTrackers = async ({ ); } - return trackers; + return harden(trackers); }; export const getBookDataTracker = async (t, auctioneerPublicFacet, brand) => { From 7292068ec1783c89482cf7b6869246770bb4a5d3 Mon Sep 17 00:00:00 2001 From: anilhelvaci Date: Mon, 22 Jan 2024 09:49:23 +0300 Subject: [PATCH 14/39] chore(liquidationVisibility): #4 improve testing tools --- .../test-liquidationVisibility.js | 40 +++++++++---------- .../test/liquidationVisibility/tools.js | 14 +++++++ 2 files changed, 33 insertions(+), 21 deletions(-) diff --git a/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js b/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js index 720893167d8..1bbd7c5465e 100644 --- a/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js +++ b/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js @@ -35,7 +35,8 @@ import { startAuctionClock, getDataFromVstorage, openVault, - getMetricTrackers, adjustVault + getMetricTrackers, + adjustVault, closeVault } from "./tools.js"; import { assertBidderPayout, @@ -114,27 +115,27 @@ test('test vault liquidation', async t => { auctioneerKit, } = services; - const metricsTopic = await E.get(E(reservePublicFacet).getPublicTopics()) - .metrics; + const { reserveTracker } = await getMetricTrackers({ + t, + collateralManager: aethCollateralManager, + reservePublicFacet, + }); let expectedReserveState = reserveInitialState(run.makeEmpty()); - await assertReserveState(t, metricsTopic, 'initial', expectedReserveState); + await assertReserveState(reserveTracker, 'initial', expectedReserveState); await E(reserveCreatorFacet).addIssuer(aeth.issuer, 'Aeth'); const collateralAmount = aeth.make(400n); const wantMinted = run.make(1600n); - const vaultSeat = await E(zoe).offer( - await E(aethCollateralManager).makeVaultInvitation(), - harden({ - give: { Collateral: collateralAmount }, - want: { Minted: wantMinted }, - }), - harden({ - Collateral: aeth.mint.mintPayment(collateralAmount), - }), - ); + const vaultSeat = await openVault({ + t, + cm: aethCollateralManager, + collateralAmount, + colKeyword: 'aeth', + wantMintedAmount: wantMinted, + }); // A bidder places a bid const bidAmount = run.make(2000n); @@ -176,12 +177,10 @@ test('test vault liquidation', async t => { await assertVaultCurrentDebt(t, vault, 0n); await assertVaultFactoryRewardAllocation(t, vaultFactory, 80n); - const closeSeat = await E(zoe).offer(E(vault).makeCloseInvitation()); + const closeSeat = await closeVault({ t, vault }); await E(closeSeat).getOfferResult(); - const closeProceeds = await E(closeSeat).getPayouts(); - - await assertCollateralProceeds(t, closeProceeds.Collateral, 0n); + await assertCollateralProceeds(t, closeSeat, aeth.makeEmpty()); await assertVaultCollateral(t, vault, 0n); await assertBidderPayout(t, bidderSeat, run, 320n, aeth, 400n); @@ -191,7 +190,7 @@ test('test vault liquidation', async t => { Fee: undefined, }, }; - await assertReserveState(t, metricsTopic, 'like', expectedReserveState); + await assertReserveState(reserveTracker, 'like', expectedReserveState); }); // We'll make a loan, and trigger liquidation via price changes. The interest @@ -292,11 +291,10 @@ test('Auction sells all collateral w/shortfall', async t => { await assertMintedProceeds(t, aliceVaultSeat, aliceWantMinted); await assertVaultDebtSnapshot(t, aliceNotifier, aliceWantMinted); - let totalDebt = 5250n; await collateralManagerTracker.assertChange({ numActiveVaults: 1, totalCollateral: { value: 1000n }, - totalDebt: { value: totalDebt }, + totalDebt: { value: 5250n }, }); // reduce collateral ///////////////////////////////////// diff --git a/packages/inter-protocol/test/liquidationVisibility/tools.js b/packages/inter-protocol/test/liquidationVisibility/tools.js index 0fed62186ad..01ba6780da7 100644 --- a/packages/inter-protocol/test/liquidationVisibility/tools.js +++ b/packages/inter-protocol/test/liquidationVisibility/tools.js @@ -332,6 +332,20 @@ export const adjustVault = async ({ t, vault, proposal, payment }) => { ); }; +/** + * @typedef {object} CloseVaultParams + * @property {Vault} vault + * @property {object} t + */ + +/** + * @param {CloseVaultParams} closeVaultParams + * @returns {Promise} + */ +export const closeVault = async ({ t, vault }) => { + return E(t.context.zoe).offer(E(vault).makeCloseInvitation()); +}; + /** * @typedef {object} GetTrackerParams * @property {any} t From d8af7fab902e7290f14803930380e1a8c2059d26 Mon Sep 17 00:00:00 2001 From: anilhelvaci Date: Mon, 22 Jan 2024 09:52:40 +0300 Subject: [PATCH 15/39] fix(liquidationVisibility): linting fixes --- .../test/liquidationVisibility/assertions.js | 1 - .../test-liquidationVisibility.js | 13 ++++--------- .../test/liquidationVisibility/tools.js | 1 - 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/packages/inter-protocol/test/liquidationVisibility/assertions.js b/packages/inter-protocol/test/liquidationVisibility/assertions.js index 82a29889c55..786a101264f 100644 --- a/packages/inter-protocol/test/liquidationVisibility/assertions.js +++ b/packages/inter-protocol/test/liquidationVisibility/assertions.js @@ -7,7 +7,6 @@ import { makeRatio, } from '@agoric/zoe/src/contractSupport/index.js'; import { headValue } from '../supports.js'; -import { subscriptionTracker } from '../metrics.js'; export const assertBidderPayout = async ( t, diff --git a/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js b/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js index 1bbd7c5465e..618082edef8 100644 --- a/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js +++ b/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js @@ -7,12 +7,10 @@ import { deeplyFulfilled } from '@endo/marshal'; import { makeTracer } from '@agoric/internal'; import { buildManualTimer } from '@agoric/swingset-vat/tools/manual-timer.js'; import { - ceilMultiplyBy, makeRatio, makeRatioFromAmounts, } from '@agoric/zoe/src/contractSupport/index.js'; import { documentStorageSchema } from '@agoric/governance/tools/storageDoc.js'; -import { AmountMath } from '@agoric/ertp'; import { defaultParamValues, legacyOfferResult, @@ -22,11 +20,7 @@ import { SECONDS_PER_DAY as ONE_DAY, SECONDS_PER_WEEK as ONE_WEEK, } from '../../src/proposals/econ-behaviors.js'; -import { - reserveInitialState, - subscriptionTracker, - vaultManagerMetricsTracker, -} from '../metrics.js'; +import { reserveInitialState } from '../metrics.js'; import { bid, setClockAndAdvanceNTimes, @@ -36,8 +30,9 @@ import { getDataFromVstorage, openVault, getMetricTrackers, - adjustVault, closeVault -} from "./tools.js"; + adjustVault, + closeVault, +} from './tools.js'; import { assertBidderPayout, assertCollateralProceeds, diff --git a/packages/inter-protocol/test/liquidationVisibility/tools.js b/packages/inter-protocol/test/liquidationVisibility/tools.js index 01ba6780da7..ceed4411071 100644 --- a/packages/inter-protocol/test/liquidationVisibility/tools.js +++ b/packages/inter-protocol/test/liquidationVisibility/tools.js @@ -8,7 +8,6 @@ import { eventLoopIteration } from '@agoric/internal/src/testing-utils.js'; import { TimeMath } from '@agoric/time'; import { subscribeEach } from '@agoric/notifier'; import '../../src/vaultFactory/types.js'; -import exp from 'constants'; import { withAmountUtils } from '../supports.js'; import { getRunFromFaucet, From 45cea3b435aae9c051f019b42291572f6f6c75c0 Mon Sep 17 00:00:00 2001 From: anilhelvaci Date: Mon, 22 Jan 2024 09:55:24 +0300 Subject: [PATCH 16/39] chore(liquidationVisibility): fix test names --- .../test-liquidationVisibility.js | 289 +----------------- 1 file changed, 4 insertions(+), 285 deletions(-) diff --git a/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js b/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js index 618082edef8..90a733ef798 100644 --- a/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js +++ b/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js @@ -90,7 +90,8 @@ test.before(async t => { trace(t, 'CONTEXT'); }); -test('test vault liquidation', async t => { +// Liquidation ends with a happy path +test('liq-result-scenario-1', async t => { const { zoe, run, aeth } = t.context; const manualTimer = buildManualTimer(); @@ -194,7 +195,7 @@ test('test vault liquidation', async t => { // overcollateralized 100%. Alice will withdraw enough of the overage that // she'll get caught when prices drop. // A bidder will buy at the 65% level, so there will be a shortfall. -test('Auction sells all collateral w/shortfall', async t => { +test('liq-result-scenario-2', async t => { const { zoe, aeth, run, rates: defaultRates } = t.context; // Add a vaultManager with 10000 aeth collateral at a 200 aeth/Minted rate @@ -374,286 +375,4 @@ test('Auction sells all collateral w/shortfall', async t => { // Bidder bought 800 Aeth await assertBidderPayout(t, bidderSeat, run, 115n, aeth, 700n); -}); - -test('test liquidate vault with snapshot', async t => { - const { zoe, run, aeth } = t.context; - const manualTimer = buildManualTimer(); - - const services = await setupServices( - t, - makeRatio(50n, run.brand, 10n, aeth.brand), - aeth.make(400n), - manualTimer, - undefined, - { StartFrequency: ONE_HOUR }, - ); - - const { - vaultFactory: { aethCollateralManager }, - aethTestPriceAuthority, - reserveKit: { reserveCreatorFacet }, - auctioneerKit, - } = services; - - await E(reserveCreatorFacet).addIssuer(aeth.issuer, 'Aeth'); - - const collateralAmount = aeth.make(400n); - const wantMinted = run.make(1600n); - - const vaultSeat = await E(zoe).offer( - await E(aethCollateralManager).makeVaultInvitation(), - harden({ - give: { Collateral: collateralAmount }, - want: { Minted: wantMinted }, - }), - harden({ - Collateral: aeth.mint.mintPayment(collateralAmount), - }), - ); - - // A bidder places a bid - const bidAmount = run.make(2000n); - const desired = aeth.make(400n); - await bid(t, zoe, auctioneerKit, aeth, bidAmount, desired); - - const { vault } = await legacyOfferResult(vaultSeat); - - // drop collateral price from 5:1 to 4:1 and liquidate vault - aethTestPriceAuthority.setPrice(makeRatio(40n, run.brand, 10n, aeth.brand)); - - const { startTime } = await startAuctionClock(auctioneerKit, manualTimer); - - await setClockAndAdvanceNTimes(manualTimer, 2, startTime, 2n); - - const closeSeat = await E(zoe).offer(E(vault).makeCloseInvitation()); - await E(closeSeat).getOfferResult(); - - const storage = await services.space.consume.chainStorage; - const doc = { - node: 'auction', - owner: 'the auctioneer contract', - pattern: 'mockChainStorageRoot.auction', - replacement: 'published.auction', - }; - - await documentStorageSchema(t, storage, doc); -}); - -/* -Verify the following data when extracted from vstorage: -- Vault Manager -- Collateral at time of liquidation -- Debt at time of liquidation -- Time of liquidation -*/ -test('test visibility of vault liquidation', async t => { - const { zoe, run, aeth } = t.context; - const manualTimer = buildManualTimer(); - - const services = await setupServices( - t, - makeRatio(50n, run.brand, 10n, aeth.brand), - aeth.make(400n), - manualTimer, - undefined, - { StartFrequency: ONE_HOUR }, - ); - - const { - vaultFactory: { aethCollateralManager }, - reserveKit: { reserveCreatorFacet, reservePublicFacet }, - auctioneerKit, - } = services; - - await E.get(E(reservePublicFacet).getPublicTopics()).metrics; - await E(reserveCreatorFacet).addIssuer(aeth.issuer, 'Aeth'); - - const collateralAmount = aeth.make(400n); - const wantMinted = run.make(1600n); - - const vaultSeat = await E(zoe).offer( - await E(aethCollateralManager).makeVaultInvitation(), - harden({ - give: { Collateral: collateralAmount }, - want: { Minted: wantMinted }, - }), - harden({ - Collateral: aeth.mint.mintPayment(collateralAmount), - }), - ); - - // A bidder places a bid - const bidAmount = run.make(2000n); - const desired = aeth.make(400n); - await bid(t, zoe, auctioneerKit, aeth, bidAmount, desired); - - // test is the vault data retrieved from publisher match the storage - const { - publicNotifiers: { vault: vaultNotifier }, - } = await legacyOfferResult(vaultSeat); - - const storage = await services.space.consume.chainStorage; - const node = 'vaultFactory.managers.manager0.vaults.vault0'; - const vaultDataVstorage = await getDataFromVstorage(storage, node); - - await assertVaultData(t, vaultNotifier, vaultDataVstorage); -}); - -/* -Verify the following data when extracted from vstorage: -- Auction identifier -- Auction start -- Collateral offered -- IST Target -- Collateral remaining -- Collateral sold -- IST target remaining -- IST raised -*/ -test('test visibility of auction', async t => { - const { zoe, run, aeth } = t.context; - const manualTimer = buildManualTimer(); - const timerBrand = manualTimer.getTimerBrand(); - - const services = await setupServices( - t, - makeRatio(50n, run.brand, 10n, aeth.brand), - aeth.make(400n), - manualTimer, - undefined, - { StartFrequency: ONE_HOUR }, - ); - - const { - vaultFactory: { aethCollateralManager }, - aethTestPriceAuthority, - reserveKit: { reserveCreatorFacet }, - auctioneerKit, - } = services; - - await E(reserveCreatorFacet).addIssuer(aeth.issuer, 'Aeth'); - - const collateralAmount = aeth.make(400n); - const wantMinted = run.make(1600n); - - const vaultSeat = await E(zoe).offer( - await E(aethCollateralManager).makeVaultInvitation(), - harden({ - give: { Collateral: collateralAmount }, - want: { Minted: wantMinted }, - }), - harden({ - Collateral: aeth.mint.mintPayment(collateralAmount), - }), - ); - - const bidAmount = run.make(2000n); - const desired = aeth.make(400n); - await bid(t, zoe, auctioneerKit, aeth, bidAmount, desired); - - const { - publicNotifiers: { vault: vaultNotifier }, - } = await legacyOfferResult(vaultSeat); - - // drop collateral price from 5:1 to 4:1 and start vault liquidating - aethTestPriceAuthority.setPrice(makeRatio(40n, run.brand, 10n, aeth.brand)); - const { startTime } = await startAuctionClock(auctioneerKit, manualTimer); - await assertVaultState(t, vaultNotifier, 'liquidating'); - - const auctioneerPublicTopics = await E( - auctioneerKit.publicFacet, - ).getPublicTopics(); - - const bookDataSubscriber = await E( - auctioneerKit.publicFacet, - ).getBookDataUpdates(aeth.brand); - - await assertBookData(t, bookDataSubscriber, { - collateralAvailable: aeth.make(400n), - currentPriceLevel: null, - proceedsRaised: undefined, - remainingProceedsGoal: null, - startCollateral: aeth.make(400n), - startPrice: makeRatioFromAmounts(run.make(4000000n), aeth.make(1000000n)), - startProceedsGoal: run.make(1680n), - }); - - await assertAuctioneerSchedule(t, auctioneerPublicTopics, { - activeStartTime: { - absValue: 3610n, - timerBrand, - }, - nextDescendingStepTime: { - absValue: 3610n, - timerBrand, - }, - nextStartTime: { absValue: 7210n, timerBrand }, - }); - - await setClockAndAdvanceNTimes(manualTimer, 2, startTime, 2n); - await assertVaultState(t, vaultNotifier, 'liquidated'); - - await assertBookData(t, bookDataSubscriber, { - collateralAvailable: aeth.makeEmpty(), - currentPriceLevel: null, - proceedsRaised: undefined, - remainingProceedsGoal: null, - startCollateral: aeth.makeEmpty(), - startPrice: null, - startProceedsGoal: null, - }); - - await assertAuctioneerSchedule(t, auctioneerPublicTopics, { - activeStartTime: null, - nextDescendingStepTime: { - absValue: 7210n, - timerBrand, - }, - nextStartTime: { absValue: 7210n, timerBrand }, - }); - - const bookDataKeys = [ - 'collateralAvailable', - 'currentPriceLevel', - 'proceedsRaised', - 'remainingProceedsGoal', - 'startCollateral', - 'startPrice', - 'startProceedsGoal', - ]; - await assertAuctioneerPathData( - t, - auctioneerKit.publicFacet, - aeth.brand, - 'bookData', - 'mockChainStorageRoot.auction.book0', - bookDataKeys, - ); - - const scheduleDataKeys = [ - 'activeStartTime', - 'nextDescendingStepTime', - 'nextStartTime', - ]; - await assertAuctioneerPathData( - t, - auctioneerKit.publicFacet, - undefined, - 'schedule', - 'mockChainStorageRoot.auction.schedule', - scheduleDataKeys, - ); -}); - -/* -Verify the following data when extracted from vstorage: -- Collateral sent to reserve -- Collateral returned to vault -- Debt returned to vault -- Shortfall sent to reserve -*/ - -test('test visibility of post auction distribution', async t => { - t.pass(); -}); +}); \ No newline at end of file From 89db737baf8dd042288dddf6c01f88fcdac1b808 Mon Sep 17 00:00:00 2001 From: anilhelvaci Date: Mon, 22 Jan 2024 10:01:46 +0300 Subject: [PATCH 17/39] fix(liquidationVisibility): lint fix --- .../liquidationVisibility/test-liquidationVisibility.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js b/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js index 90a733ef798..089c0c77890 100644 --- a/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js +++ b/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js @@ -10,7 +10,6 @@ import { makeRatio, makeRatioFromAmounts, } from '@agoric/zoe/src/contractSupport/index.js'; -import { documentStorageSchema } from '@agoric/governance/tools/storageDoc.js'; import { defaultParamValues, legacyOfferResult, @@ -27,7 +26,6 @@ import { setupBasics, setupServices, startAuctionClock, - getDataFromVstorage, openVault, getMetricTrackers, adjustVault, @@ -45,10 +43,6 @@ import { assertVaultLocked, assertVaultSeatExited, assertVaultState, - assertBookData, - assertAuctioneerSchedule, - assertAuctioneerPathData, - assertVaultData, assertMintedProceeds, } from './assertions.js'; import { Phase } from '../vaultFactory/driver.js'; @@ -375,4 +369,4 @@ test('liq-result-scenario-2', async t => { // Bidder bought 800 Aeth await assertBidderPayout(t, bidderSeat, run, 115n, aeth, 700n); -}); \ No newline at end of file +}); From 9fefcf0a24156c24e5376998371f12ecd260c2d7 Mon Sep 17 00:00:00 2001 From: anilhelvaci Date: Mon, 22 Jan 2024 14:30:44 +0300 Subject: [PATCH 18/39] chore(liquidationVisibility): #4 implement `assertNodeInStorage` --- .../test/liquidationVisibility/assertions.js | 12 ++++++ .../test-liquidationVisibility.js | 8 +++- .../test-visibilityAssertions.js | 37 +++++++++++++++++++ .../test/liquidationVisibility/tools.js | 4 ++ 4 files changed, 59 insertions(+), 2 deletions(-) create mode 100644 packages/inter-protocol/test/liquidationVisibility/test-visibilityAssertions.js diff --git a/packages/inter-protocol/test/liquidationVisibility/assertions.js b/packages/inter-protocol/test/liquidationVisibility/assertions.js index 786a101264f..c2a1f4c656d 100644 --- a/packages/inter-protocol/test/liquidationVisibility/assertions.js +++ b/packages/inter-protocol/test/liquidationVisibility/assertions.js @@ -7,6 +7,8 @@ import { makeRatio, } from '@agoric/zoe/src/contractSupport/index.js'; import { headValue } from '../supports.js'; +import exp from "constants"; +import { getDataFromVstorage } from "./tools.js"; export const assertBidderPayout = async ( t, @@ -213,3 +215,13 @@ export const assertVaultData = async ( const auctioneerBookData = await E(vaultDataSubscriber).getUpdateSince(); t.deepEqual(auctioneerBookData.value, vaultDataVstorage[0][1]); }; + +export const assertNodeInStorage = async ({ + t, + rootNode, + desiredNode, + expected, +}) => { + const [...storageData] = await getDataFromVstorage(rootNode, desiredNode); + t.is(storageData.length !== 0, expected); +}; diff --git a/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js b/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js index 089c0c77890..d7cd5fd1493 100644 --- a/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js +++ b/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js @@ -29,8 +29,8 @@ import { openVault, getMetricTrackers, adjustVault, - closeVault, -} from './tools.js'; + closeVault, getDataFromVstorage +} from "./tools.js"; import { assertBidderPayout, assertCollateralProceeds, @@ -103,8 +103,12 @@ test('liq-result-scenario-1', async t => { aethTestPriceAuthority, reserveKit: { reserveCreatorFacet, reservePublicFacet }, auctioneerKit, + chainStorage, } = services; + const storageData = await getDataFromVstorage(chainStorage, 'whatever'); + t.log(storageData); + const { reserveTracker } = await getMetricTrackers({ t, collateralManager: aethCollateralManager, diff --git a/packages/inter-protocol/test/liquidationVisibility/test-visibilityAssertions.js b/packages/inter-protocol/test/liquidationVisibility/test-visibilityAssertions.js new file mode 100644 index 00000000000..7926216625a --- /dev/null +++ b/packages/inter-protocol/test/liquidationVisibility/test-visibilityAssertions.js @@ -0,0 +1,37 @@ +import { test } from '@agoric/zoe/tools/prepare-test-env-ava.js'; +import { E } from '@endo/far'; +import { makeImportContext} from "@agoric/smart-wallet/src/marshal-contexts.js"; +import { makeMockChainStorageRoot } from '../supports.js'; +import { getDataFromVstorage } from './tools.js'; +import { assertNodeInStorage } from "./assertions.js"; + +const { + fromBoard: { toCapData }, +} = makeImportContext(); + +const writeToStorage = async (storageNode, data) => { + await E(storageNode).setValue( + JSON.stringify(toCapData(JSON.stringify(data))), + ); +}; + +test('storage-node-created', async t => { + const storageRoot = makeMockChainStorageRoot(); + + await assertNodeInStorage({ + t, + rootNode: storageRoot, + desiredNode: 'test', + expected: false, + }); + + const testNode = await E(storageRoot).makeChildNode('test'); + await writeToStorage(testNode, { dummy: 'foo' }); + + await assertNodeInStorage({ + t, + rootNode: storageRoot, + desiredNode: 'test', + expected: true, + }); +}); diff --git a/packages/inter-protocol/test/liquidationVisibility/tools.js b/packages/inter-protocol/test/liquidationVisibility/tools.js index ceed4411071..7b8c1ea7c78 100644 --- a/packages/inter-protocol/test/liquidationVisibility/tools.js +++ b/packages/inter-protocol/test/liquidationVisibility/tools.js @@ -151,6 +151,7 @@ export const setupServices = async ( * AuctioneerKit, * ManualPriceAuthority, * CollateralManager, + * chainStorage, * ]} */ const [ @@ -161,6 +162,7 @@ export const setupServices = async ( auctioneerKit, priceAuthority, aethCollateralManager, + chainStorage, ] = await Promise.all([ E(consume.agoricNames).lookup('instance', 'VaultFactoryGovernor'), vaultFactoryCreatorFacetP, @@ -169,6 +171,7 @@ export const setupServices = async ( consume.auctioneerKit, /** @type {Promise} */ (consume.priceAuthority), E(aethVaultManagerP).getPublicFacet(), + space.consume.chainStorage, ]); trace(t, 'pa', { governorInstance, @@ -205,6 +208,7 @@ export const setupServices = async ( auctioneerKit, priceAuthorityAdmin, aethTestPriceAuthority, + chainStorage, }; }; From e81df0100cbbc6ffb2a325f359a08eb35634b14b Mon Sep 17 00:00:00 2001 From: anilhelvaci Date: Mon, 22 Jan 2024 15:15:36 +0300 Subject: [PATCH 19/39] fix(liquidationVisibility): #4 lint fix --- .../inter-protocol/test/liquidationVisibility/assertions.js | 3 +-- .../test/liquidationVisibility/test-liquidationVisibility.js | 5 +++-- .../test/liquidationVisibility/test-visibilityAssertions.js | 5 ++--- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/packages/inter-protocol/test/liquidationVisibility/assertions.js b/packages/inter-protocol/test/liquidationVisibility/assertions.js index c2a1f4c656d..90ddc0ae16c 100644 --- a/packages/inter-protocol/test/liquidationVisibility/assertions.js +++ b/packages/inter-protocol/test/liquidationVisibility/assertions.js @@ -7,8 +7,7 @@ import { makeRatio, } from '@agoric/zoe/src/contractSupport/index.js'; import { headValue } from '../supports.js'; -import exp from "constants"; -import { getDataFromVstorage } from "./tools.js"; +import { getDataFromVstorage } from './tools.js'; export const assertBidderPayout = async ( t, diff --git a/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js b/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js index d7cd5fd1493..66779a77b83 100644 --- a/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js +++ b/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js @@ -29,8 +29,9 @@ import { openVault, getMetricTrackers, adjustVault, - closeVault, getDataFromVstorage -} from "./tools.js"; + closeVault, + getDataFromVstorage, +} from './tools.js'; import { assertBidderPayout, assertCollateralProceeds, diff --git a/packages/inter-protocol/test/liquidationVisibility/test-visibilityAssertions.js b/packages/inter-protocol/test/liquidationVisibility/test-visibilityAssertions.js index 7926216625a..7321a9d24ed 100644 --- a/packages/inter-protocol/test/liquidationVisibility/test-visibilityAssertions.js +++ b/packages/inter-protocol/test/liquidationVisibility/test-visibilityAssertions.js @@ -1,9 +1,8 @@ import { test } from '@agoric/zoe/tools/prepare-test-env-ava.js'; import { E } from '@endo/far'; -import { makeImportContext} from "@agoric/smart-wallet/src/marshal-contexts.js"; +import { makeImportContext } from '@agoric/smart-wallet/src/marshal-contexts.js'; import { makeMockChainStorageRoot } from '../supports.js'; -import { getDataFromVstorage } from './tools.js'; -import { assertNodeInStorage } from "./assertions.js"; +import { assertNodeInStorage } from './assertions.js'; const { fromBoard: { toCapData }, From 419c810604cd20fd1d7f546edb2ff21185ebfe39 Mon Sep 17 00:00:00 2001 From: JorgeLopes-BytePitch Date: Mon, 22 Jan 2024 15:56:15 +0000 Subject: [PATCH 20/39] feat(liquidationVisibility): write postAuctionState to Vstorage BREAKING CHANGE: the getVaultId method of vaults interface was updated to getVaultState, which will return the vault phase as well --- .../inter-protocol/src/vaultFactory/vault.js | 9 ++- .../src/vaultFactory/vaultManager.js | 63 +++++++++++++++++-- 2 files changed, 64 insertions(+), 8 deletions(-) diff --git a/packages/inter-protocol/src/vaultFactory/vault.js b/packages/inter-protocol/src/vaultFactory/vault.js index f0f0629d4bd..0a5fe3ec4b8 100644 --- a/packages/inter-protocol/src/vaultFactory/vault.js +++ b/packages/inter-protocol/src/vaultFactory/vault.js @@ -131,7 +131,7 @@ export const VaultI = M.interface('Vault', { getCurrentDebt: M.call().returns(AmountShape), getNormalizedDebt: M.call().returns(AmountShape), getVaultSeat: M.call().returns(SeatShape), - getVaultId: M.call().returns(M.any()), + getVaultState: M.call().returns(M.any()), initVaultKit: M.call(SeatShape, StorageNodeShape).returns(M.promise()), liquidated: M.call().returns(undefined), liquidating: M.call().returns(undefined), @@ -598,8 +598,11 @@ export const prepareVault = (baggage, makeRecorderKit, zcf) => { return this.state.vaultSeat; }, - getVaultId() { - return this.state.idInManager; + getVaultState() { + return { + idInManager: this.state.idInManager, + phase: this.state.phase, + }; }, /** diff --git a/packages/inter-protocol/src/vaultFactory/vaultManager.js b/packages/inter-protocol/src/vaultFactory/vaultManager.js index ce33dec9d60..c12a1f651b6 100644 --- a/packages/inter-protocol/src/vaultFactory/vaultManager.js +++ b/packages/inter-protocol/src/vaultFactory/vaultManager.js @@ -217,8 +217,8 @@ export const watchQuoteNotifier = async (notifierP, watcher, ...args) => { * * @typedef {{ * vaultId: string; - * collateral: Amount<'nat'>; - * debt: Amount<'nat'>; + * collateral: Amount<'nat'> | null; + * debt: Amount<'nat'> | null; * phase: string; * }[]} PostAuctionState * @@ -1314,6 +1314,7 @@ export const prepareVaultManagerKit = ( ); let planAuctionResult; + let postAuctionProceeds = []; trace(`LiqV after long wait`, proceeds); try { const { plan, vaultsInPlan } = helper.planProceedsDistribution( @@ -1343,6 +1344,18 @@ export const prepareVaultManagerKit = ( collateralSold: plan.collateralSold, collateralRemaining: plan.collatRemaining, }; + + for (let i = 0; i < plan.transfersToVault.length; i++) { + const transfers = plan.transfersToVault[i]; + const keyword = Object.keys(transfers[1])[0]; + const amount = Object.values(transfers[1])[0]; + const vault = vaultsInPlan[transfers[0]]; + + postAuctionProceeds.push({ + transfers: { keyword, amount }, + vault, + }); + } } catch (err) { console.error('🚨 Error distributing proceeds:', err); } @@ -1359,9 +1372,9 @@ export const prepareVaultManagerKit = ( /** @type {PreAuctionState} */ let preAuctionState = []; for (const [key, value] of vaultData.entries()) { - const vaultIdInManager = await E(key).getVaultId(); + const { idInManager } = await E(key).getVaultState(); const preAuctionVaultData = { - vaultId: `vault${vaultIdInManager}`, + vaultId: `vault${idInManager}`, collateral: value.collateralAmount, debt: value.debtAmount, }; @@ -1374,9 +1387,49 @@ export const prepareVaultManagerKit = ( endTime: auctionSchedule.nextAuctionSchedule?.endTime, }; + /** @type {PostAuctionState} */ + const postAuctionState = []; + for (let i = 0; i < postAuctionProceeds.length; i++) { + const { + vault, + transfers: { keyword, amount }, + } = postAuctionProceeds[i]; + + const { idInManager, phase } = await E(vault).getVaultState(); + + const vaultPostAuctionState = { + vaultId: `vault${idInManager}`, + phase, + collateral: null, + debt: null, + }; + + let postAuctionPayload; + switch (keyword) { + case 'Collateral': + postAuctionPayload = { + ...vaultPostAuctionState, + collateral: amount, + }; + break; + case 'Minted': + postAuctionPayload = { + ...vaultPostAuctionState, + debt: amount, + }; + break; + default: + postAuctionPayload = { + ...vaultPostAuctionState, + }; + } + + postAuctionState.push(postAuctionPayload); + } + void helper.writeLiquidations(liquidationRecorderKits, { preAuctionState, - postAuctionState: [], + postAuctionState, auctionResultState, }); }, From 637a28280e6f99ff6983172900efe09f7fcc7bb9 Mon Sep 17 00:00:00 2001 From: JorgeLopes-BytePitch Date: Mon, 22 Jan 2024 18:57:21 +0000 Subject: [PATCH 21/39] chore(liquidationVisibility): update helper methods, type definitions and names --- .../src/vaultFactory/vaultManager.js | 252 ++++++++++-------- 1 file changed, 141 insertions(+), 111 deletions(-) diff --git a/packages/inter-protocol/src/vaultFactory/vaultManager.js b/packages/inter-protocol/src/vaultFactory/vaultManager.js index c12a1f651b6..e1ffadc9a8b 100644 --- a/packages/inter-protocol/src/vaultFactory/vaultManager.js +++ b/packages/inter-protocol/src/vaultFactory/vaultManager.js @@ -230,6 +230,18 @@ export const watchQuoteNotifier = async (notifierP, watcher, ...args) => { * collateralRemaining?: Amount<'nat'>; * endTime?: import('@agoric/time').TimestampRecord | null; * }} AuctionResultState + * + * @typedef {{ + * preAuctionRecorderKit: import('@agoric/zoe/src/contractSupport/recorder.js').RecorderKit; + * postAuctionRecorderKit: import('@agoric/zoe/src/contractSupport/recorder.js').RecorderKit; + * auctionResultRecorderKit: import('@agoric/zoe/src/contractSupport/recorder.js').RecorderKit; + * }} LiquidationRecorderKits + * + * @typedef {{ + * preAuctionState: PreAuctionState; + * postAuctionState: PostAuctionState; + * auctionResultState: AuctionResultState; + * }} LiquidationPayloads */ // any b/c will be filled after start() const collateralEphemera = makeEphemeraProvider(() => /** @type {any} */ ({})); @@ -682,16 +694,8 @@ export const prepareVaultManagerKit = ( }, /** - * @param {{ - * preAuctionRecorderKit: any; - * postAuctionRecorderKit: any; - * auctionResultRecorderKit: any; - * }} liquidationRecorderKits - * @param {{ - * preAuctionState: PreAuctionState; - * postAuctionState: any; - * auctionResultState: any; - * }} liquidationPayloads + * @param {LiquidationRecorderKits} liquidationRecorderKits + * @param {LiquidationPayloads} liquidationPayloads */ writeLiquidations(liquidationRecorderKits, liquidationPayloads) { const { @@ -708,6 +712,118 @@ export const prepareVaultManagerKit = ( auctionResultRecorderKit.recorder.writeFinal(auctionResultState); }, + /** + * @param {{ absValue: any }} timestamp + * @returns {Promise} + */ + async makeLiquidationRecorderKits(timestamp) { + const { + state: { liquidationsStorageNode }, + } = this; + + const timestampStorageNode = E(liquidationsStorageNode).makeChildNode( + `${timestamp.absValue}`, + ); + + const [ + preAuctionStorageNode, + postAuctionStorageNode, + auctionResultStorageNode, + ] = await Promise.all([ + E(E(timestampStorageNode).makeChildNode('vaults')).makeChildNode( + 'preAuction', + ), + E(E(timestampStorageNode).makeChildNode('vaults')).makeChildNode( + 'postAuction', + ), + E(timestampStorageNode).makeChildNode('auctionResult'), + ]); + + const preAuctionRecorderKit = makeRecorderKit(preAuctionStorageNode); + const postAuctionRecorderKit = makeRecorderKit( + postAuctionStorageNode, + ); + const auctionResultRecorderKit = makeRecorderKit( + auctionResultStorageNode, + ); + + return { + preAuctionRecorderKit, + postAuctionRecorderKit, + auctionResultRecorderKit, + }; + }, + + /** + * @param {MapStore< + * Vault, + * { collateralAmount: Amount<'nat'>; debtAmount: Amount<'nat'> } + * >} vaultData + * @returns {Promise} + */ + async getPreAuctionState(vaultData) { + let preAuctionState = []; + for (const [key, value] of vaultData.entries()) { + const { idInManager } = await E(key).getVaultState(); + const preAuctionVaultData = { + vaultId: `vault${idInManager}`, + collateral: value.collateralAmount, + debt: value.debtAmount, + }; + preAuctionState.push(preAuctionVaultData); + } + + return preAuctionState; + }, + + /** + * @param {{ + * keyword: string; + * amount: Amount<'nat'>; + * vault: Vault; + * }[]} postAuctionProceeds + * @returns {Promise} + */ + async getPostAuctionState(postAuctionProceeds) { + const postAuctionState = []; + for (let i = 0; i < postAuctionProceeds.length; i++) { + const { keyword, amount, vault } = postAuctionProceeds[i]; + + const { idInManager, phase } = await E(vault).getVaultState(); + + const vaultPostAuctionState = { + vaultId: `vault${idInManager}`, + phase, + collateral: null, + debt: null, + }; + + let postAuctionPayload; + switch (keyword) { + case 'Collateral': + postAuctionPayload = { + ...vaultPostAuctionState, + collateral: amount, + }; + break; + case 'Minted': + postAuctionPayload = { + ...vaultPostAuctionState, + debt: amount, + }; + break; + default: + postAuctionPayload = { + ...vaultPostAuctionState, + }; + } + + postAuctionState.push(postAuctionPayload); + } + + return postAuctionState; + }, + /** * This is designed to tolerate an incomplete plan, in case * calculateDistributionPlan encounters an error during its calculation. @@ -874,45 +990,6 @@ export const prepareVaultManagerKit = ( // liqSeat should be empty at this point, except that funds are sent // asynchronously to the reserve. }, - - /** @param {{ absValue: BigInt }} timestamp */ - async liquidationRecorderKits(timestamp) { - const { - state: { liquidationsStorageNode }, - } = this; - - const timestampStorageNode = E(liquidationsStorageNode).makeChildNode( - `${timestamp.absValue}`, - ); - - const [ - preAuctionStorageNode, - postAuctionStorageNode, - auctionResultStorageNode, - ] = await Promise.all([ - E(E(timestampStorageNode).makeChildNode('vaults')).makeChildNode( - 'preAuction', - ), - E(E(timestampStorageNode).makeChildNode('vaults')).makeChildNode( - 'postAuction', - ), - E(timestampStorageNode).makeChildNode('auctionResult'), - ]); - - const preAuctionRecorderKit = makeRecorderKit(preAuctionStorageNode); - const postAuctionRecorderKit = makeRecorderKit( - postAuctionStorageNode, - ); - const auctionResultRecorderKit = makeRecorderKit( - auctionResultStorageNode, - ); - - return { - preAuctionRecorderKit, - postAuctionRecorderKit, - auctionResultRecorderKit, - }; - }, }, manager: { @@ -1303,7 +1380,7 @@ export const prepareVaultManagerKit = ( // and the difference between startingRate and lowestRate. const [liquidationRecorderKits, auctionSchedule, proceeds] = await Promise.all([ - helper.liquidationRecorderKits(timestamp), + helper.makeLiquidationRecorderKits(timestamp), E(auctionPF).getSchedules(), deposited, userSeatPromise, @@ -1313,8 +1390,8 @@ export const prepareVaultManagerKit = ( this.state.collateralBrand, ); - let planAuctionResult; - let postAuctionProceeds = []; + let auctionResultProceeds; + const postAuctionProceeds = []; trace(`LiqV after long wait`, proceeds); try { const { plan, vaultsInPlan } = helper.planProceedsDistribution( @@ -1337,7 +1414,7 @@ export const prepareVaultManagerKit = ( vaultsInPlan, }); - planAuctionResult = { + auctionResultProceeds = { collateralForReserve: plan.collateralForReserve, shortfallToReserve: plan.shortfallToReserve, mintedProceeds: plan.mintedProceeds, @@ -1346,13 +1423,14 @@ export const prepareVaultManagerKit = ( }; for (let i = 0; i < plan.transfersToVault.length; i++) { - const transfers = plan.transfersToVault[i]; - const keyword = Object.keys(transfers[1])[0]; - const amount = Object.values(transfers[1])[0]; - const vault = vaultsInPlan[transfers[0]]; + const transfer = plan.transfersToVault[i]; + const keyword = Object.keys(transfer[1])[0]; + const amount = Object.values(transfer[1])[0]; + const vault = vaultsInPlan[transfer[0]]; postAuctionProceeds.push({ - transfers: { keyword, amount }, + keyword, + amount, vault, }); } @@ -1369,63 +1447,15 @@ export const prepareVaultManagerKit = ( void helper.writeMetrics(); - /** @type {PreAuctionState} */ - let preAuctionState = []; - for (const [key, value] of vaultData.entries()) { - const { idInManager } = await E(key).getVaultState(); - const preAuctionVaultData = { - vaultId: `vault${idInManager}`, - collateral: value.collateralAmount, - debt: value.debtAmount, - }; - preAuctionState.push(preAuctionVaultData); - } - - /** @type {AuctionResultState} */ const auctionResultState = { - ...planAuctionResult, + ...auctionResultProceeds, endTime: auctionSchedule.nextAuctionSchedule?.endTime, }; - /** @type {PostAuctionState} */ - const postAuctionState = []; - for (let i = 0; i < postAuctionProceeds.length; i++) { - const { - vault, - transfers: { keyword, amount }, - } = postAuctionProceeds[i]; - - const { idInManager, phase } = await E(vault).getVaultState(); - - const vaultPostAuctionState = { - vaultId: `vault${idInManager}`, - phase, - collateral: null, - debt: null, - }; - - let postAuctionPayload; - switch (keyword) { - case 'Collateral': - postAuctionPayload = { - ...vaultPostAuctionState, - collateral: amount, - }; - break; - case 'Minted': - postAuctionPayload = { - ...vaultPostAuctionState, - debt: amount, - }; - break; - default: - postAuctionPayload = { - ...vaultPostAuctionState, - }; - } - - postAuctionState.push(postAuctionPayload); - } + const [preAuctionState, postAuctionState] = await Promise.all([ + helper.getPreAuctionState(vaultData), + helper.getPostAuctionState(postAuctionProceeds), + ]); void helper.writeLiquidations(liquidationRecorderKits, { preAuctionState, From 40893619fb10a3cea1f2612a42a886854df0552a Mon Sep 17 00:00:00 2001 From: JorgeLopes-BytePitch Date: Mon, 22 Jan 2024 19:41:34 +0000 Subject: [PATCH 22/39] fix(liquidationVisibility): lint fix --- .../src/vaultFactory/vaultManager.js | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/packages/inter-protocol/src/vaultFactory/vaultManager.js b/packages/inter-protocol/src/vaultFactory/vaultManager.js index e1ffadc9a8b..69a064b562b 100644 --- a/packages/inter-protocol/src/vaultFactory/vaultManager.js +++ b/packages/inter-protocol/src/vaultFactory/vaultManager.js @@ -52,6 +52,7 @@ import { } from '@agoric/zoe/src/contractSupport/index.js'; import { PriceQuoteShape, SeatShape } from '@agoric/zoe/src/typeGuards.js'; import { E } from '@endo/eventual-send'; +import { TimestampShape } from '@agoric/time'; import { AuctionPFShape } from '../auction/auctioneer.js'; import { checkDebtLimit, @@ -64,7 +65,6 @@ import { calculateMinimumCollateralization, minimumPrice } from './math.js'; import { makePrioritizedVaults } from './prioritizedVaults.js'; import { Phase, prepareVault } from './vault.js'; import { calculateDistributionPlan } from './proceeds.js'; -import { TimestampShape } from '@agoric/time'; const { details: X, Fail, quote: q } = assert; @@ -707,9 +707,17 @@ export const prepareVaultManagerKit = ( const { preAuctionState, postAuctionState, auctionResultState } = liquidationPayloads; - preAuctionRecorderKit.recorder.writeFinal(preAuctionState); - postAuctionRecorderKit.recorder.writeFinal(postAuctionState); - auctionResultRecorderKit.recorder.writeFinal(auctionResultState); + const preAuctionResult = E(preAuctionRecorderKit.recorder).write( + preAuctionState, + ); + const postAuctionResult = E(postAuctionRecorderKit.recorder).write( + postAuctionState, + ); + const auctionResultResult = E( + auctionResultRecorderKit.recorder, + ).write(auctionResultState); + + return { preAuctionResult, postAuctionResult, auctionResultResult }; }, /** @@ -762,7 +770,8 @@ export const prepareVaultManagerKit = ( * @returns {Promise} */ async getPreAuctionState(vaultData) { - let preAuctionState = []; + const preAuctionState = []; + await null; for (const [key, value] of vaultData.entries()) { const { idInManager } = await E(key).getVaultState(); const preAuctionVaultData = { @@ -786,7 +795,8 @@ export const prepareVaultManagerKit = ( */ async getPostAuctionState(postAuctionProceeds) { const postAuctionState = []; - for (let i = 0; i < postAuctionProceeds.length; i++) { + await null; + for (let i = 0; i < postAuctionProceeds.length; i += 1) { const { keyword, amount, vault } = postAuctionProceeds[i]; const { idInManager, phase } = await E(vault).getVaultState(); @@ -1292,7 +1302,7 @@ export const prepareVaultManagerKit = ( }, /** * @param {ERef} auctionPF - * @param {{ absValue: BigInt }} timestamp + * @param {{ absValue: bigint }} timestamp */ async liquidateVaults(auctionPF, timestamp) { const { state, facets } = this; @@ -1422,7 +1432,7 @@ export const prepareVaultManagerKit = ( collateralRemaining: plan.collatRemaining, }; - for (let i = 0; i < plan.transfersToVault.length; i++) { + for (let i = 0; i < plan.transfersToVault.length; i += 1) { const transfer = plan.transfersToVault[i]; const keyword = Object.keys(transfer[1])[0]; const amount = Object.values(transfer[1])[0]; From a5815ae490c2c2a3b828688f7bedad6263612d09 Mon Sep 17 00:00:00 2001 From: anilhelvaci Date: Tue, 23 Jan 2024 11:17:32 +0300 Subject: [PATCH 23/39] chore(liquidationVisibility): #4 test skeleton is ready --- .../test/liquidationVisibility/assertions.js | 27 +++++++ .../test-liquidationVisibility.js | 76 ++++++++++++++++++- .../test-visibilityAssertions.js | 15 +++- .../test/liquidationVisibility/tools.js | 4 +- 4 files changed, 115 insertions(+), 7 deletions(-) diff --git a/packages/inter-protocol/test/liquidationVisibility/assertions.js b/packages/inter-protocol/test/liquidationVisibility/assertions.js index 90ddc0ae16c..2102de65f18 100644 --- a/packages/inter-protocol/test/liquidationVisibility/assertions.js +++ b/packages/inter-protocol/test/liquidationVisibility/assertions.js @@ -6,6 +6,7 @@ import { ceilMultiplyBy, makeRatio, } from '@agoric/zoe/src/contractSupport/index.js'; +import { TimeMath } from '@agoric/time'; import { headValue } from '../supports.js'; import { getDataFromVstorage } from './tools.js'; @@ -224,3 +225,29 @@ export const assertNodeInStorage = async ({ const [...storageData] = await getDataFromVstorage(rootNode, desiredNode); t.is(storageData.length !== 0, expected); }; + +// Currently supports only one collateral manager +export const assertLiqNodeForAuctionCreated = async ({ + t, + rootNode, + auctioneerPF, + auctionType = 'next', // 'live' is the other option + expected = false, +}) => { + const schedules = await E(auctioneerPF).getSchedules(); + const { startTime, startDelay } = schedules[`${auctionType}AuctionSchedule`]; + const nominalStart = TimeMath.subtractAbsRel(startTime, startDelay); + + await assertNodeInStorage({ + t, + rootNode, + desiredNode: `vaultFactory.managers.manager0.liquidations.${nominalStart}`, + expected, + }); +}; + +export const assertStorageData = async ({ t, path, storageRoot, expected }) => { + /** @type Array */ + const [[, value]] = await getDataFromVstorage(storageRoot, path); + t.deepEqual(JSON.parse(value), expected); +}; diff --git a/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js b/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js index 66779a77b83..97ed6885e9c 100644 --- a/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js +++ b/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js @@ -10,6 +10,7 @@ import { makeRatio, makeRatioFromAmounts, } from '@agoric/zoe/src/contractSupport/index.js'; +import { documentStorageSchema } from '@agoric/governance/tools/storageDoc.js'; import { defaultParamValues, legacyOfferResult, @@ -45,6 +46,9 @@ import { assertVaultSeatExited, assertVaultState, assertMintedProceeds, + assertNodeInStorage, + assertLiqNodeForAuctionCreated, + assertStorageData, } from './assertions.js'; import { Phase } from '../vaultFactory/driver.js'; @@ -227,6 +231,7 @@ test('liq-result-scenario-2', async t => { aethTestPriceAuthority, reserveKit: { reserveCreatorFacet, reservePublicFacet }, auctioneerKit, + chainStorage, } = services; await E(reserveCreatorFacet).addIssuer(aeth.issuer, 'Aeth'); @@ -317,6 +322,13 @@ test('liq-result-scenario-2', async t => { totalCollateral: { value: 700n }, }); + // TODO: UNCOMMENT THIS WHEN SOURCE IS READY + // await assertLiqNodeForAuctionCreated({ + // t, + // rootNode: chainStorage, + // auctioneerPF: auctioneerKit.publicFacet, + // }); + await E(aethTestPriceAuthority).setPrice( makeRatio(70n, run.brand, 10n, aeth.brand), ); @@ -327,10 +339,12 @@ test('liq-result-scenario-2', async t => { const desired = aeth.make(700n); const bidderSeat = await bid(t, zoe, auctioneerKit, aeth, bidAmount, desired); - const { startTime: start1, time: now1 } = await startAuctionClock( - auctioneerKit, - manualTimer, - ); + const { + startTime: start1, + time: now1, + endTime, + } = await startAuctionClock(auctioneerKit, manualTimer); + let currentTime = now1; await collateralManagerTracker.assertChange({ @@ -343,6 +357,22 @@ test('liq-result-scenario-2', async t => { // expect Alice to be liquidated because her collateral is too low. await assertVaultState(t, aliceNotifier, Phase.LIQUIDATING); + // TODO: Check vaults.preAuction here + // await assertStorageData({ + // t, + // storageRoot: chainStorage, + // path: `vaultFactory.managers.manager0.liquidations.${now1}.preAuction`, // now1 is the nominal start time + // expected: [ + // [ + // 'vault0', + // { + // collateral: aeth.make(700n), + // debt: await E(aliceVault).getCurrentDebt(), + // }, + // ], + // ], + // }); + currentTime = await setClockAndAdvanceNTimes(manualTimer, 2, start1, 2n); await assertVaultState(t, aliceNotifier, Phase.LIQUIDATED); @@ -374,4 +404,42 @@ test('liq-result-scenario-2', async t => { // Bidder bought 800 Aeth await assertBidderPayout(t, bidderSeat, run, 115n, aeth, 700n); + + // TODO: Check vaults.postAuction and auctionResults here + // await assertStorageData({ + // t, + // storageRoot: chainStorage, + // path: `vaultFactory.managers.manager0.liquidations.${now1}.postAuction`, // now1 is the nominal start time + // expected: [ + // [ + // 'vault0', + // { + // collateral: aeth.makeEmpty(), + // debt: run.makeEmpty(), + // phase: Phase.LIQUIDATED, + // }, + // ], + // ], + // }); + + // FIXME: https://github.com/Jorge-Lopes/agoric-sdk-liquidation-visibility/issues/3#issuecomment-1905488335 + // await assertStorageData({ + // t, + // storageRoot: chainStorage, + // path: `vaultFactory.managers.manager0.liquidations.${now1}.auctionResult`, // now1 is the nominal start time + // expected: { + // collateralForReserve: aeth.makeEmpty(), + // shortfallToReserve: run.make(2065n), + // mintedProceeds: run.make(3185n), + // collateralSold: aeth.make(700n), + // collateralRemaining: aeth.makeEmpty(), + // endTime, + // }, + // }); + + // TODO: Snapshot here + // await documentStorageSchema(t, chainStorage, { + // note: 'Scenario 2 Liquidation Visibility Snapshot', + // node: `vaultFactory.managers.manager0.liquidations.${now1}`, + // }); }); diff --git a/packages/inter-protocol/test/liquidationVisibility/test-visibilityAssertions.js b/packages/inter-protocol/test/liquidationVisibility/test-visibilityAssertions.js index 7321a9d24ed..6738c0fd1c1 100644 --- a/packages/inter-protocol/test/liquidationVisibility/test-visibilityAssertions.js +++ b/packages/inter-protocol/test/liquidationVisibility/test-visibilityAssertions.js @@ -2,7 +2,7 @@ import { test } from '@agoric/zoe/tools/prepare-test-env-ava.js'; import { E } from '@endo/far'; import { makeImportContext } from '@agoric/smart-wallet/src/marshal-contexts.js'; import { makeMockChainStorageRoot } from '../supports.js'; -import { assertNodeInStorage } from './assertions.js'; +import { assertNodeInStorage, assertStorageData } from './assertions.js'; const { fromBoard: { toCapData }, @@ -34,3 +34,16 @@ test('storage-node-created', async t => { expected: true, }); }); + +test('storage-assert-data', async t => { + const storageRoot = makeMockChainStorageRoot(); + const testNode = await E(storageRoot).makeChildNode('dummyNode'); + await writeToStorage(testNode, { dummy: 'foo' }); + + await assertStorageData({ + t, + path: 'dummyNode', + storageRoot, + expected: { dummy: 'foo' }, + }); +}); diff --git a/packages/inter-protocol/test/liquidationVisibility/tools.js b/packages/inter-protocol/test/liquidationVisibility/tools.js index 7b8c1ea7c78..13d8f06bc3e 100644 --- a/packages/inter-protocol/test/liquidationVisibility/tools.js +++ b/packages/inter-protocol/test/liquidationVisibility/tools.js @@ -236,7 +236,7 @@ export const setClockAndAdvanceNTimes = async ( export const startAuctionClock = async (auctioneerKit, manualTimer) => { const schedule = await E(auctioneerKit.creatorFacet).getSchedule(); const priceDelay = await E(auctioneerKit.publicFacet).getPriceLockPeriod(); - const { startTime, startDelay } = schedule.nextAuctionSchedule; + const { startTime, startDelay, endTime } = schedule.nextAuctionSchedule; const nominalStart = TimeMath.subtractAbsRel(startTime, startDelay); const priceLockTime = TimeMath.subtractAbsRel(nominalStart, priceDelay); await manualTimer.advanceTo(TimeMath.absValue(priceLockTime)); @@ -244,7 +244,7 @@ export const startAuctionClock = async (auctioneerKit, manualTimer) => { await manualTimer.advanceTo(TimeMath.absValue(nominalStart)); await eventLoopIteration(); - return { startTime, time: nominalStart }; + return { startTime, time: nominalStart, endTime }; }; export const bid = async (t, zoe, auctioneerKit, aeth, bidAmount, desired) => { From 51eb92904c44534d88ebf0e4a52cb444b81fdeb5 Mon Sep 17 00:00:00 2001 From: anilhelvaci Date: Tue, 23 Jan 2024 11:35:21 +0300 Subject: [PATCH 24/39] chore(liquidationVisibility): #4 add marshaller for comparing data from the vstorage --- .../test/liquidationVisibility/assertions.js | 16 ++++++++++++++-- .../test-liquidationVisibility.js | 4 ++++ .../test/liquidationVisibility/tools.js | 6 +++++- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/packages/inter-protocol/test/liquidationVisibility/assertions.js b/packages/inter-protocol/test/liquidationVisibility/assertions.js index 2102de65f18..e2014000ebb 100644 --- a/packages/inter-protocol/test/liquidationVisibility/assertions.js +++ b/packages/inter-protocol/test/liquidationVisibility/assertions.js @@ -9,6 +9,7 @@ import { import { TimeMath } from '@agoric/time'; import { headValue } from '../supports.js'; import { getDataFromVstorage } from './tools.js'; +import exp from "constants"; export const assertBidderPayout = async ( t, @@ -246,8 +247,19 @@ export const assertLiqNodeForAuctionCreated = async ({ }); }; -export const assertStorageData = async ({ t, path, storageRoot, expected }) => { +export const assertStorageData = async ({ + t, + path, + storageRoot, + board, + expected, +}) => { + /** @typedef {import('@endo/marshal').Marshal} Marshal */ + /** @type Marshal */ + const marshaller = await E(board).getReadonlyMarshaller(); + const expectedCapData = marshaller.toCapData(expected); + /** @type Array */ const [[, value]] = await getDataFromVstorage(storageRoot, path); - t.deepEqual(JSON.parse(value), expected); + t.deepEqual(value, JSON.stringify(expectedCapData)); }; diff --git a/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js b/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js index 97ed6885e9c..4d4c62791d7 100644 --- a/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js +++ b/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js @@ -232,6 +232,7 @@ test('liq-result-scenario-2', async t => { reserveKit: { reserveCreatorFacet, reservePublicFacet }, auctioneerKit, chainStorage, + board, } = services; await E(reserveCreatorFacet).addIssuer(aeth.issuer, 'Aeth'); @@ -361,6 +362,7 @@ test('liq-result-scenario-2', async t => { // await assertStorageData({ // t, // storageRoot: chainStorage, + // board, // path: `vaultFactory.managers.manager0.liquidations.${now1}.preAuction`, // now1 is the nominal start time // expected: [ // [ @@ -409,6 +411,7 @@ test('liq-result-scenario-2', async t => { // await assertStorageData({ // t, // storageRoot: chainStorage, + // board, // path: `vaultFactory.managers.manager0.liquidations.${now1}.postAuction`, // now1 is the nominal start time // expected: [ // [ @@ -426,6 +429,7 @@ test('liq-result-scenario-2', async t => { // await assertStorageData({ // t, // storageRoot: chainStorage, + // board, // path: `vaultFactory.managers.manager0.liquidations.${now1}.auctionResult`, // now1 is the nominal start time // expected: { // collateralForReserve: aeth.makeEmpty(), diff --git a/packages/inter-protocol/test/liquidationVisibility/tools.js b/packages/inter-protocol/test/liquidationVisibility/tools.js index 13d8f06bc3e..da576f86acc 100644 --- a/packages/inter-protocol/test/liquidationVisibility/tools.js +++ b/packages/inter-protocol/test/liquidationVisibility/tools.js @@ -152,6 +152,7 @@ export const setupServices = async ( * ManualPriceAuthority, * CollateralManager, * chainStorage, + * board, * ]} */ const [ @@ -163,6 +164,7 @@ export const setupServices = async ( priceAuthority, aethCollateralManager, chainStorage, + board, ] = await Promise.all([ E(consume.agoricNames).lookup('instance', 'VaultFactoryGovernor'), vaultFactoryCreatorFacetP, @@ -171,7 +173,8 @@ export const setupServices = async ( consume.auctioneerKit, /** @type {Promise} */ (consume.priceAuthority), E(aethVaultManagerP).getPublicFacet(), - space.consume.chainStorage, + consume.chainStorage, + consume.board, ]); trace(t, 'pa', { governorInstance, @@ -209,6 +212,7 @@ export const setupServices = async ( priceAuthorityAdmin, aethTestPriceAuthority, chainStorage, + board, }; }; From ba953482ceba5b2807ca64a1dc2343d8731393c8 Mon Sep 17 00:00:00 2001 From: JorgeLopes-BytePitch Date: Tue, 23 Jan 2024 19:03:27 +0000 Subject: [PATCH 25/39] fix(liquidationVisibility): update sequence to write auction state to Vstorage and its structure The helper methods built for this purpose became unnecessary and were removed. --- .../src/vaultFactory/vaultManager.js | 208 +++++------------- .../vaultFactory/test-vaultLiquidation.js | 46 ++++ 2 files changed, 98 insertions(+), 156 deletions(-) diff --git a/packages/inter-protocol/src/vaultFactory/vaultManager.js b/packages/inter-protocol/src/vaultFactory/vaultManager.js index 69a064b562b..7b802f0ab9a 100644 --- a/packages/inter-protocol/src/vaultFactory/vaultManager.js +++ b/packages/inter-protocol/src/vaultFactory/vaultManager.js @@ -209,20 +209,16 @@ export const watchQuoteNotifier = async (notifierP, watcher, ...args) => { */ /** - * @typedef {{ - * vaultId: string; - * collateral: Amount<'nat'>; - * debt: Amount<'nat'>; - * }[]} PreAuctionState + * @typedef {( + * | string + * | { collateralAmount: Amount<'nat'>; debtAmount: Amount<'nat'> } + * )[][]} PreAuctionState * - * @typedef {{ - * vaultId: string; - * collateral: Amount<'nat'> | null; - * debt: Amount<'nat'> | null; - * phase: string; - * }[]} PostAuctionState + * @typedef {(string | { transfer: AmountKeywordRecord; phase: string })[][]} PostAuctionState * * @typedef {{ + * collateralOffered?: Amount<'nat'>; + * istTarget?: Amount<'nat'>; * collateralForReserve?: Amount<'nat'>; * shortfallToReserve?: Amount<'nat'>; * mintedProceeds?: Amount<'nat'>; @@ -236,12 +232,6 @@ export const watchQuoteNotifier = async (notifierP, watcher, ...args) => { * postAuctionRecorderKit: import('@agoric/zoe/src/contractSupport/recorder.js').RecorderKit; * auctionResultRecorderKit: import('@agoric/zoe/src/contractSupport/recorder.js').RecorderKit; * }} LiquidationRecorderKits - * - * @typedef {{ - * preAuctionState: PreAuctionState; - * postAuctionState: PostAuctionState; - * auctionResultState: AuctionResultState; - * }} LiquidationPayloads */ // any b/c will be filled after start() const collateralEphemera = makeEphemeraProvider(() => /** @type {any} */ ({})); @@ -693,33 +683,6 @@ export const prepareVaultManagerKit = ( return E(metricsTopicKit.recorder).write(payload); }, - /** - * @param {LiquidationRecorderKits} liquidationRecorderKits - * @param {LiquidationPayloads} liquidationPayloads - */ - writeLiquidations(liquidationRecorderKits, liquidationPayloads) { - const { - preAuctionRecorderKit, - postAuctionRecorderKit, - auctionResultRecorderKit, - } = liquidationRecorderKits; - - const { preAuctionState, postAuctionState, auctionResultState } = - liquidationPayloads; - - const preAuctionResult = E(preAuctionRecorderKit.recorder).write( - preAuctionState, - ); - const postAuctionResult = E(postAuctionRecorderKit.recorder).write( - postAuctionState, - ); - const auctionResultResult = E( - auctionResultRecorderKit.recorder, - ).write(auctionResultState); - - return { preAuctionResult, postAuctionResult, auctionResultResult }; - }, - /** * @param {{ absValue: any }} timestamp * @returns {Promise} @@ -762,78 +725,6 @@ export const prepareVaultManagerKit = ( }; }, - /** - * @param {MapStore< - * Vault, - * { collateralAmount: Amount<'nat'>; debtAmount: Amount<'nat'> } - * >} vaultData - * @returns {Promise} - */ - async getPreAuctionState(vaultData) { - const preAuctionState = []; - await null; - for (const [key, value] of vaultData.entries()) { - const { idInManager } = await E(key).getVaultState(); - const preAuctionVaultData = { - vaultId: `vault${idInManager}`, - collateral: value.collateralAmount, - debt: value.debtAmount, - }; - preAuctionState.push(preAuctionVaultData); - } - - return preAuctionState; - }, - - /** - * @param {{ - * keyword: string; - * amount: Amount<'nat'>; - * vault: Vault; - * }[]} postAuctionProceeds - * @returns {Promise} - */ - async getPostAuctionState(postAuctionProceeds) { - const postAuctionState = []; - await null; - for (let i = 0; i < postAuctionProceeds.length; i += 1) { - const { keyword, amount, vault } = postAuctionProceeds[i]; - - const { idInManager, phase } = await E(vault).getVaultState(); - - const vaultPostAuctionState = { - vaultId: `vault${idInManager}`, - phase, - collateral: null, - debt: null, - }; - - let postAuctionPayload; - switch (keyword) { - case 'Collateral': - postAuctionPayload = { - ...vaultPostAuctionState, - collateral: amount, - }; - break; - case 'Minted': - postAuctionPayload = { - ...vaultPostAuctionState, - debt: amount, - }; - break; - default: - postAuctionPayload = { - ...vaultPostAuctionState, - }; - } - - postAuctionState.push(postAuctionPayload); - } - - return postAuctionState; - }, - /** * This is designed to tolerate an incomplete plan, in case * calculateDistributionPlan encounters an error during its calculation. @@ -1371,7 +1262,7 @@ export const prepareVaultManagerKit = ( helper.markLiquidating(totalDebt, totalCollateral); void helper.writeMetrics(); - const { userSeatPromise, deposited } = await E.when( + const makeDeposit = E.when( E(auctionPF).makeDepositInvitation(), depositInvitation => offerTo( @@ -1385,23 +1276,36 @@ export const prepareVaultManagerKit = ( ), ); - // This is expected to wait for the duration of the auction, which - // is controlled by the auction parameters startFrequency, clockStep, - // and the difference between startingRate and lowestRate. - const [liquidationRecorderKits, auctionSchedule, proceeds] = + const [{ userSeatPromise, deposited }, liquidationRecorderKits] = await Promise.all([ + makeDeposit, helper.makeLiquidationRecorderKits(timestamp), - E(auctionPF).getSchedules(), - deposited, - userSeatPromise, ]); + /** @type PreAuctionState */ + const preAuctionState = [...vaultData.entries()].map( + ([vault, data]) => [ + `vault${vault.getVaultState().idInManager}`, + { ...data }, + ], + ); + + // This is expected to wait for the duration of the auction, which + // is controlled by the auction parameters startFrequency, clockStep, + // and the difference between startingRate and lowestRate. + const [auctionSchedule, proceeds] = await Promise.all([ + E(auctionPF).getSchedules(), + deposited, + userSeatPromise, + E(liquidationRecorderKits.preAuctionRecorderKit.recorder).write( + preAuctionState, + ), + ]); + const { storedCollateralQuote } = collateralEphemera( this.state.collateralBrand, ); - let auctionResultProceeds; - const postAuctionProceeds = []; trace(`LiqV after long wait`, proceeds); try { const { plan, vaultsInPlan } = helper.planProceedsDistribution( @@ -1424,26 +1328,34 @@ export const prepareVaultManagerKit = ( vaultsInPlan, }); - auctionResultProceeds = { + /** @type AuctionResultState */ + const auctionResultState = { + collateralOffered: totalCollateral, + istTarget: totalDebt, collateralForReserve: plan.collateralForReserve, shortfallToReserve: plan.shortfallToReserve, mintedProceeds: plan.mintedProceeds, collateralSold: plan.collateralSold, collateralRemaining: plan.collatRemaining, + endTime: auctionSchedule.nextAuctionSchedule?.endTime, }; - - for (let i = 0; i < plan.transfersToVault.length; i += 1) { - const transfer = plan.transfersToVault[i]; - const keyword = Object.keys(transfer[1])[0]; - const amount = Object.values(transfer[1])[0]; - const vault = vaultsInPlan[transfer[0]]; - - postAuctionProceeds.push({ - keyword, - amount, - vault, - }); - } + void E( + liquidationRecorderKits.auctionResultRecorderKit.recorder, + ).write(auctionResultState); + + /** @type PostAuctionState */ + const postAuctionState = plan.transfersToVault.map( + ([id, transfer]) => [ + `vault${vaultsInPlan[id].getVaultState().idInManager}`, + { + transfer, + phase: vaultsInPlan[id].getVaultState().phase, + }, + ], + ); + void E( + liquidationRecorderKits.postAuctionRecorderKit.recorder, + ).write(postAuctionState); } catch (err) { console.error('🚨 Error distributing proceeds:', err); } @@ -1456,22 +1368,6 @@ export const prepareVaultManagerKit = ( } void helper.writeMetrics(); - - const auctionResultState = { - ...auctionResultProceeds, - endTime: auctionSchedule.nextAuctionSchedule?.endTime, - }; - - const [preAuctionState, postAuctionState] = await Promise.all([ - helper.getPreAuctionState(vaultData), - helper.getPostAuctionState(postAuctionProceeds), - ]); - - void helper.writeLiquidations(liquidationRecorderKits, { - preAuctionState, - postAuctionState, - auctionResultState, - }); }, }, }, diff --git a/packages/inter-protocol/test/vaultFactory/test-vaultLiquidation.js b/packages/inter-protocol/test/vaultFactory/test-vaultLiquidation.js index 50d4030f733..d013af32393 100644 --- a/packages/inter-protocol/test/vaultFactory/test-vaultLiquidation.js +++ b/packages/inter-protocol/test/vaultFactory/test-vaultLiquidation.js @@ -248,6 +248,7 @@ const setupServices = async ( return { zoe, + space, timer, governor: g, vaultFactory: v, @@ -510,6 +511,27 @@ test('price drop', async t => { Fee: undefined, }, }); + + + const storage = await services.space.consume.chainStorage; + const node = 'vaultFactory.managers.manager0.liquidations.3600.auctionResult'; + + // @ts-ignore + const illustration = [...storage.keys()].sort().map( + /** @type {(k: string) => [string, unknown]} */ + key => [ + key.replace('mockChainStorageRoot.', 'published.'), + // @ts-ignore + storage.getBody(key), + ], + ); + + const pruned = illustration.filter( + node ? ([key, _]) => key.startsWith(`published.${node}`) : _entry => true, + ); + + console.log(pruned[0][1]); + }); test('price falls precipitously', async t => { @@ -2768,6 +2790,30 @@ test('refund to one of two loans', async t => { Fee: undefined, }, }); + + + const storage = await services.space.consume.chainStorage; + const node = 'vaultFactory.managers.manager0.liquidations.3600.vaults.'; + + // @ts-ignore + const illustration = [...storage.keys()].sort().map( + /** @type {(k: string) => [string, unknown]} */ + key => [ + key.replace('mockChainStorageRoot.', 'published.'), + // @ts-ignore + storage.getBody(key), + ], + ); + + const pruned = illustration.filter( + node ? ([key, _]) => key.startsWith(`published.${node}`) : _entry => true, + ); + + console.log('LOG: preAuction', pruned[1][1]); + + console.log('LOG: postAuction', pruned[0][1]); + + }); test('Bug 7784 reconstitute both', async t => { From 59e155ba1ea24ca39f28dd4725dc4d523920fbcf Mon Sep 17 00:00:00 2001 From: anilhelvaci Date: Wed, 24 Jan 2024 13:17:16 +0300 Subject: [PATCH 26/39] chore(liquidationVisibility): sample test for `preAuction` and `postAuction` data fields --- .../test-visibilityAssertions.js | 45 ++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/packages/inter-protocol/test/liquidationVisibility/test-visibilityAssertions.js b/packages/inter-protocol/test/liquidationVisibility/test-visibilityAssertions.js index 6738c0fd1c1..c215c43f474 100644 --- a/packages/inter-protocol/test/liquidationVisibility/test-visibilityAssertions.js +++ b/packages/inter-protocol/test/liquidationVisibility/test-visibilityAssertions.js @@ -1,6 +1,7 @@ import { test } from '@agoric/zoe/tools/prepare-test-env-ava.js'; -import { E } from '@endo/far'; +import { E, Far } from '@endo/far'; import { makeImportContext } from '@agoric/smart-wallet/src/marshal-contexts.js'; +import { makeScalarBigMapStore } from '@agoric/vat-data'; import { makeMockChainStorageRoot } from '../supports.js'; import { assertNodeInStorage, assertStorageData } from './assertions.js'; @@ -44,6 +45,48 @@ test('storage-assert-data', async t => { t, path: 'dummyNode', storageRoot, + board: {}, expected: { dummy: 'foo' }, }); }); + +test('map-test-auction', async t => { + const vaultData = makeScalarBigMapStore('Vaults'); + + vaultData.init( + Far('key', { getId: () => 1, getPhase: () => 'liquidated' }), + harden({ + collateral: 19n, + debt: 18n, + }), + ); + vaultData.init( + Far('key1', { getId: () => 2, getPhase: () => 'liquidated' }), + harden({ + collateral: 19n, + debt: 18n, + }), + ); + vaultData.init( + Far('key2', { getId: () => 3, getPhase: () => 'liquidated' }), + harden({ + collateral: 19n, + debt: 18n, + }), + ); + vaultData.init( + Far('key3', { getId: () => 4, getPhase: () => 'liquidated' }), + harden({ + collateral: 19n, + debt: 18n, + }), + ); + + const preAuction = [...vaultData.entries()].map(([vault, data]) => [ + vault.getId(), + { ...data, phase: vault.getPhase() } + ]); + t.log(preAuction); + + t.pass(); +}); From 1a17dd4e5534650995f9e501850a003ff2eb8bba Mon Sep 17 00:00:00 2001 From: anilhelvaci Date: Wed, 24 Jan 2024 13:41:39 +0300 Subject: [PATCH 27/39] chore(liquidationVisibility): make sure `assertStorageData` works --- .../test/liquidationVisibility/assertions.js | 8 +------ .../test-visibilityAssertions.js | 21 ++++++++++--------- 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/packages/inter-protocol/test/liquidationVisibility/assertions.js b/packages/inter-protocol/test/liquidationVisibility/assertions.js index e2014000ebb..e619571aed3 100644 --- a/packages/inter-protocol/test/liquidationVisibility/assertions.js +++ b/packages/inter-protocol/test/liquidationVisibility/assertions.js @@ -251,15 +251,9 @@ export const assertStorageData = async ({ t, path, storageRoot, - board, expected, }) => { - /** @typedef {import('@endo/marshal').Marshal} Marshal */ - /** @type Marshal */ - const marshaller = await E(board).getReadonlyMarshaller(); - const expectedCapData = marshaller.toCapData(expected); - /** @type Array */ const [[, value]] = await getDataFromVstorage(storageRoot, path); - t.deepEqual(value, JSON.stringify(expectedCapData)); + t.deepEqual(value, expected); }; diff --git a/packages/inter-protocol/test/liquidationVisibility/test-visibilityAssertions.js b/packages/inter-protocol/test/liquidationVisibility/test-visibilityAssertions.js index c215c43f474..1aa50b93460 100644 --- a/packages/inter-protocol/test/liquidationVisibility/test-visibilityAssertions.js +++ b/packages/inter-protocol/test/liquidationVisibility/test-visibilityAssertions.js @@ -1,17 +1,15 @@ import { test } from '@agoric/zoe/tools/prepare-test-env-ava.js'; import { E, Far } from '@endo/far'; -import { makeImportContext } from '@agoric/smart-wallet/src/marshal-contexts.js'; import { makeScalarBigMapStore } from '@agoric/vat-data'; +import { defaultMarshaller } from '@agoric/internal/src/storage-test-utils.js'; +import { makeFakeBoard } from '@agoric/vats/tools/board-utils.js'; +import { AmountMath, makeIssuerKit } from '@agoric/ertp'; import { makeMockChainStorageRoot } from '../supports.js'; import { assertNodeInStorage, assertStorageData } from './assertions.js'; -const { - fromBoard: { toCapData }, -} = makeImportContext(); - const writeToStorage = async (storageNode, data) => { await E(storageNode).setValue( - JSON.stringify(toCapData(JSON.stringify(data))), + JSON.stringify(defaultMarshaller.toCapData(harden(data))), ); }; @@ -38,15 +36,18 @@ test('storage-node-created', async t => { test('storage-assert-data', async t => { const storageRoot = makeMockChainStorageRoot(); + const moolaKit = makeIssuerKit('Moola'); + const testNode = await E(storageRoot).makeChildNode('dummyNode'); - await writeToStorage(testNode, { dummy: 'foo' }); + await writeToStorage(testNode, { + moolaForReserve: AmountMath.make(moolaKit.brand, 100n), + }); await assertStorageData({ t, path: 'dummyNode', storageRoot, - board: {}, - expected: { dummy: 'foo' }, + expected: { moolaForReserve: AmountMath.make(moolaKit.brand, 100n) }, }); }); @@ -84,7 +85,7 @@ test('map-test-auction', async t => { const preAuction = [...vaultData.entries()].map(([vault, data]) => [ vault.getId(), - { ...data, phase: vault.getPhase() } + { ...data, phase: vault.getPhase() }, ]); t.log(preAuction); From b7cdb1386ab5190c7b9368b5c6b53202cae4a6e6 Mon Sep 17 00:00:00 2001 From: anilhelvaci Date: Thu, 25 Jan 2024 14:01:30 +0300 Subject: [PATCH 28/39] chore(liquidationVisibility): #4 add test for case 2b, uncomment assertions for running the tests --- .../test/liquidationVisibility/assertions.js | 13 +- .../test-liquidationVisibility.js | 386 +++++++++++++++--- .../test-visibilityAssertions.js | 1 - 3 files changed, 336 insertions(+), 64 deletions(-) diff --git a/packages/inter-protocol/test/liquidationVisibility/assertions.js b/packages/inter-protocol/test/liquidationVisibility/assertions.js index e619571aed3..0f4e228a50b 100644 --- a/packages/inter-protocol/test/liquidationVisibility/assertions.js +++ b/packages/inter-protocol/test/liquidationVisibility/assertions.js @@ -9,7 +9,6 @@ import { import { TimeMath } from '@agoric/time'; import { headValue } from '../supports.js'; import { getDataFromVstorage } from './tools.js'; -import exp from "constants"; export const assertBidderPayout = async ( t, @@ -247,13 +246,13 @@ export const assertLiqNodeForAuctionCreated = async ({ }); }; -export const assertStorageData = async ({ - t, - path, - storageRoot, - expected, -}) => { +export const assertStorageData = async ({ t, path, storageRoot, expected }) => { /** @type Array */ const [[, value]] = await getDataFromVstorage(storageRoot, path); t.deepEqual(value, expected); }; + +export const assertVaultNotification = async ({ t, notifier, expected }) => { + const { value } = await E(notifier).getUpdateSince(); + t.like(value, expected); +}; diff --git a/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js b/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js index 4d4c62791d7..7162b524872 100644 --- a/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js +++ b/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js @@ -11,6 +11,8 @@ import { makeRatioFromAmounts, } from '@agoric/zoe/src/contractSupport/index.js'; import { documentStorageSchema } from '@agoric/governance/tools/storageDoc.js'; +import { AmountMath } from '@agoric/ertp'; +import { eventLoopIteration } from '@agoric/internal/src/testing-utils.js'; import { defaultParamValues, legacyOfferResult, @@ -46,9 +48,9 @@ import { assertVaultSeatExited, assertVaultState, assertMintedProceeds, - assertNodeInStorage, assertLiqNodeForAuctionCreated, assertStorageData, + assertVaultNotification, } from './assertions.js'; import { Phase } from '../vaultFactory/driver.js'; @@ -232,7 +234,6 @@ test('liq-result-scenario-2', async t => { reserveKit: { reserveCreatorFacet, reservePublicFacet }, auctioneerKit, chainStorage, - board, } = services; await E(reserveCreatorFacet).addIssuer(aeth.issuer, 'Aeth'); @@ -324,11 +325,11 @@ test('liq-result-scenario-2', async t => { }); // TODO: UNCOMMENT THIS WHEN SOURCE IS READY - // await assertLiqNodeForAuctionCreated({ - // t, - // rootNode: chainStorage, - // auctioneerPF: auctioneerKit.publicFacet, - // }); + await assertLiqNodeForAuctionCreated({ + t, + rootNode: chainStorage, + auctioneerPF: auctioneerKit.publicFacet, + }); await E(aethTestPriceAuthority).setPrice( makeRatio(70n, run.brand, 10n, aeth.brand), @@ -359,21 +360,20 @@ test('liq-result-scenario-2', async t => { await assertVaultState(t, aliceNotifier, Phase.LIQUIDATING); // TODO: Check vaults.preAuction here - // await assertStorageData({ - // t, - // storageRoot: chainStorage, - // board, - // path: `vaultFactory.managers.manager0.liquidations.${now1}.preAuction`, // now1 is the nominal start time - // expected: [ - // [ - // 'vault0', - // { - // collateral: aeth.make(700n), - // debt: await E(aliceVault).getCurrentDebt(), - // }, - // ], - // ], - // }); + await assertStorageData({ + t, + storageRoot: chainStorage, + path: `vaultFactory.managers.manager0.liquidations.${now1}.preAuction`, // now1 is the nominal start time + expected: [ + [ + 'vault0', + { + collateralAmount: aeth.make(700n), + debtAmount: await E(aliceVault).getCurrentDebt(), + }, + ], + ], + }); currentTime = await setClockAndAdvanceNTimes(manualTimer, 2, start1, 2n); @@ -408,42 +408,316 @@ test('liq-result-scenario-2', async t => { await assertBidderPayout(t, bidderSeat, run, 115n, aeth, 700n); // TODO: Check vaults.postAuction and auctionResults here - // await assertStorageData({ - // t, - // storageRoot: chainStorage, - // board, - // path: `vaultFactory.managers.manager0.liquidations.${now1}.postAuction`, // now1 is the nominal start time - // expected: [ - // [ - // 'vault0', - // { - // collateral: aeth.makeEmpty(), - // debt: run.makeEmpty(), - // phase: Phase.LIQUIDATED, - // }, - // ], - // ], - // }); + await assertStorageData({ + t, + storageRoot: chainStorage, + path: `vaultFactory.managers.manager0.liquidations.${now1}.postAuction`, // now1 is the nominal start time + expected: [ + [ + 'vault0', + { + collateral: aeth.makeEmpty(), + debt: run.makeEmpty(), + phase: Phase.LIQUIDATED, + }, + ], + ], + }); // FIXME: https://github.com/Jorge-Lopes/agoric-sdk-liquidation-visibility/issues/3#issuecomment-1905488335 - // await assertStorageData({ - // t, - // storageRoot: chainStorage, - // board, - // path: `vaultFactory.managers.manager0.liquidations.${now1}.auctionResult`, // now1 is the nominal start time - // expected: { - // collateralForReserve: aeth.makeEmpty(), - // shortfallToReserve: run.make(2065n), - // mintedProceeds: run.make(3185n), - // collateralSold: aeth.make(700n), - // collateralRemaining: aeth.makeEmpty(), - // endTime, - // }, - // }); + await assertStorageData({ + t, + storageRoot: chainStorage, + path: `vaultFactory.managers.manager0.liquidations.${now1}.auctionResult`, // now1 is the nominal start time + expected: { + collateralForReserve: aeth.makeEmpty(), + shortfallToReserve: run.make(2065n), + mintedProceeds: run.make(3185n), + collateralSold: aeth.make(700n), + collateralRemaining: aeth.makeEmpty(), + endTime, + }, + }); + + // TODO: Snapshot here + await documentStorageSchema(t, chainStorage, { + note: 'Scenario 2 Liquidation Visibility Snapshot', + node: `vaultFactory.managers.manager0.liquidations.${now1}`, + }); +}); + +test('liq-result-scenario-3', async t => { + const { zoe, aeth, run, rates: defaultRates } = t.context; + + const rates = harden({ + ...defaultRates, + interestRate: run.makeRatio(0n), + liquidationMargin: run.makeRatio(150n), + }); + t.context.rates = rates; + + const manualTimer = buildManualTimer(); + const services = await setupServices( + t, + makeRatio(1500n, run.brand, 100n, aeth.brand), + aeth.make(1n), + manualTimer, + ONE_WEEK, + 500n, + { StartFrequency: ONE_HOUR }, + ); + + const { + vaultFactory: { aethCollateralManager }, + auctioneerKit, + aethTestPriceAuthority, + reserveKit: { reserveCreatorFacet, reservePublicFacet }, + chainStorage, + } = services; + await E(reserveCreatorFacet).addIssuer(aeth.issuer, 'Aeth'); + + const { reserveTracker, collateralManagerTracker } = await getMetricTrackers({ + t, + collateralManager: aethCollateralManager, + reservePublicFacet, + }); + + await collateralManagerTracker.assertInitial({ + // present + numActiveVaults: 0, + numLiquidatingVaults: 0, + totalCollateral: aeth.make(0n), + totalDebt: run.make(0n), + retainedCollateral: aeth.make(0n), + + // running + numLiquidationsCompleted: 0, + numLiquidationsAborted: 0, + totalOverageReceived: run.make(0n), + totalProceedsReceived: run.make(0n), + totalCollateralSold: aeth.make(0n), + liquidatingCollateral: aeth.make(0n), + liquidatingDebt: run.make(0n), + totalShortfallReceived: run.make(0n), + lockedQuote: null, + }); + + // ALICE takes out a loan //////////////////////// + + // a loan of 95 with 5% fee produces a debt of 100. + const aliceCollateralAmount = aeth.make(15n); + const aliceWantMinted = run.make(95n); + /** @type {UserSeat} */ + const aliceVaultSeat = await openVault({ + t, + cm: aethCollateralManager, + collateralAmount: aliceCollateralAmount, + colKeyword: 'aeth', + wantMintedAmount: aliceWantMinted, + }); + const { + vault: aliceVault, + publicNotifiers: { vault: aliceNotifier }, + } = await legacyOfferResult(aliceVaultSeat); + + await assertVaultCurrentDebt(t, aliceVault, aliceWantMinted); + await assertMintedProceeds(t, aliceVaultSeat, aliceWantMinted); + + await assertVaultDebtSnapshot(t, aliceNotifier, aliceWantMinted); + await assertVaultState(t, aliceNotifier, Phase.ACTIVE); + + await collateralManagerTracker.assertChange({ + numActiveVaults: 1, + totalDebt: { value: 100n }, + totalCollateral: { value: 15n }, + }); + + // BOB takes out a loan //////////////////////// + const bobCollateralAmount = aeth.make(48n); + const bobWantMinted = run.make(150n); + /** @type {UserSeat} */ + const bobVaultSeat = await openVault({ + t, + cm: aethCollateralManager, + collateralAmount: bobCollateralAmount, + colKeyword: 'aeth', + wantMintedAmount: bobWantMinted, + }); + const { + vault: bobVault, + publicNotifiers: { vault: bobNotifier }, + } = await legacyOfferResult(bobVaultSeat); + + await assertVaultCurrentDebt(t, bobVault, bobWantMinted); + await assertMintedProceeds(t, bobVaultSeat, bobWantMinted); + + await assertVaultDebtSnapshot(t, bobNotifier, bobWantMinted); + await assertVaultState(t, bobNotifier, Phase.ACTIVE); + + await collateralManagerTracker.assertChange({ + numActiveVaults: 2, + totalDebt: { value: 258n }, + totalCollateral: { value: 63n }, + }); + + // A BIDDER places a BID ////////////////////////// + const bidAmount = run.make(100n); + const desired = aeth.make(8n); + const bidderSeat = await bid(t, zoe, auctioneerKit, aeth, bidAmount, desired); + + // price falls + await aethTestPriceAuthority.setPrice( + makeRatio(400n, run.brand, 100n, aeth.brand), + ); + await eventLoopIteration(); + + // TODO: assert node not created + await assertLiqNodeForAuctionCreated({ + t, + rootNode: chainStorage, + auctioneerPF: auctioneerKit.publicFacet, + }); + + const { startTime, time, endTime } = await startAuctionClock( + auctioneerKit, + manualTimer, + ); + + await assertVaultState(t, aliceNotifier, Phase.LIQUIDATING); + await assertVaultState(t, bobNotifier, Phase.LIQUIDATING); + + // TODO: PreAuction Here + await assertStorageData({ + t, + storageRoot: chainStorage, + path: `vaultFactory.managers.manager0.liquidations.${time}.preAuction`, // time is the nominal start time + expected: [ + [ + 'vault0', // Alice's vault + { + collateralAmount: aliceCollateralAmount, + debtAmount: await E(aliceVault).getCurrentDebt(), + }, + ], + [ + 'vault1', // Bob's vault + { + collateral: bobCollateralAmount, + debt: await E(bobVault).getCurrentDebt(), + }, + ], + ], + }); + + await collateralManagerTracker.assertChange({ + lockedQuote: makeRatioFromAmounts( + aeth.make(1_000_000n), + run.make(4_000_000n), + ), + }); + + await collateralManagerTracker.assertChange({ + numActiveVaults: 0, + liquidatingDebt: { value: 258n }, + liquidatingCollateral: { value: 63n }, + numLiquidatingVaults: 2, + lockedQuote: null, + }); + + await setClockAndAdvanceNTimes(manualTimer, 2n, startTime, 2n); + + await collateralManagerTracker.assertChange({ + numActiveVaults: 1, + liquidatingDebt: { value: 0n }, + liquidatingCollateral: { value: 0n }, + totalDebt: { value: 158n }, + totalCollateral: { value: 44n }, + totalProceedsReceived: { value: 34n }, + totalShortfallReceived: { value: 66n }, + totalCollateralSold: { value: 8n }, + numLiquidatingVaults: 0, + numLiquidationsCompleted: 1, + numLiquidationsAborted: 1, + }); + + await assertVaultNotification({ + t, + notifier: aliceNotifier, + expected: { + vaultState: Phase.LIQUIDATED, + locked: aeth.makeEmpty(), + }, + }); + + // Reduce Bob's collateral by liquidation penalty + // bob's share is 7 * 158/258, which rounds up to 5 + const recoveredBobCollateral = AmountMath.subtract( + bobCollateralAmount, + aeth.make(5n), + ); + + await assertVaultNotification({ + t, + notifier: bobNotifier, + expected: { + vaultState: Phase.ACTIVE, + locked: recoveredBobCollateral, + debtSnapshot: { debt: run.make(158n) }, + }, + }); + + await assertBidderPayout(t, bidderSeat, run, 66n, aeth, 8n); + + await assertReserveState(reserveTracker, 'like', { + allocations: { + Aeth: aeth.make(12n), + Fee: undefined, + }, + }); + + // TODO: PostAuction here + await assertStorageData({ + t, + storageRoot: chainStorage, + path: `vaultFactory.managers.manager0.liquidations.${time}.postAuction`, // time is the nominal start time + expected: [ + [ + 'vault0', // Alice got liquidated + { + collateral: aeth.makeEmpty(), + debt: run.makeEmpty(), + phase: Phase.LIQUIDATED, + }, + ], + [ + 'vault1', // Bob got reinstated + { + collateral: recoveredBobCollateral, + debt: run.make(158n), + phase: Phase.ACTIVE, + }, + ], + ], + }); + + // TODO: AuctionResults here + await assertStorageData({ + t, + storageRoot: chainStorage, + path: `vaultFactory.managers.manager0.liquidations.${time}.auctionResult`, // now1 is the nominal start time + expected: { + collateralForReserve: aeth.make(12n), + shortfallToReserve: run.make(66n), + mintedProceeds: run.make(34n), + collateralSold: aeth.make(8n), + collateralRemaining: aeth.make(5n), + endTime, + }, + }); // TODO: Snapshot here - // await documentStorageSchema(t, chainStorage, { - // note: 'Scenario 2 Liquidation Visibility Snapshot', - // node: `vaultFactory.managers.manager0.liquidations.${now1}`, - // }); + await documentStorageSchema(t, chainStorage, { + note: 'Scenario 3 Liquidation Visibility Snapshot', + node: `vaultFactory.managers.manager0.liquidations.${time}`, + }); }); diff --git a/packages/inter-protocol/test/liquidationVisibility/test-visibilityAssertions.js b/packages/inter-protocol/test/liquidationVisibility/test-visibilityAssertions.js index 1aa50b93460..02d58055885 100644 --- a/packages/inter-protocol/test/liquidationVisibility/test-visibilityAssertions.js +++ b/packages/inter-protocol/test/liquidationVisibility/test-visibilityAssertions.js @@ -2,7 +2,6 @@ import { test } from '@agoric/zoe/tools/prepare-test-env-ava.js'; import { E, Far } from '@endo/far'; import { makeScalarBigMapStore } from '@agoric/vat-data'; import { defaultMarshaller } from '@agoric/internal/src/storage-test-utils.js'; -import { makeFakeBoard } from '@agoric/vats/tools/board-utils.js'; import { AmountMath, makeIssuerKit } from '@agoric/ertp'; import { makeMockChainStorageRoot } from '../supports.js'; import { assertNodeInStorage, assertStorageData } from './assertions.js'; From b9068599e8958b6e73b19d4fc6fba894b8bd0808 Mon Sep 17 00:00:00 2001 From: JorgeLopes-BytePitch Date: Thu, 25 Jan 2024 11:02:00 +0000 Subject: [PATCH 29/39] fix(liquidationVisibility): update postAuctionState structure and change to writeFinal recorder method --- .../src/vaultFactory/vaultManager.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/inter-protocol/src/vaultFactory/vaultManager.js b/packages/inter-protocol/src/vaultFactory/vaultManager.js index 7b802f0ab9a..3056733ce2f 100644 --- a/packages/inter-protocol/src/vaultFactory/vaultManager.js +++ b/packages/inter-protocol/src/vaultFactory/vaultManager.js @@ -214,7 +214,7 @@ export const watchQuoteNotifier = async (notifierP, watcher, ...args) => { * | { collateralAmount: Amount<'nat'>; debtAmount: Amount<'nat'> } * )[][]} PreAuctionState * - * @typedef {(string | { transfer: AmountKeywordRecord; phase: string })[][]} PostAuctionState + * @typedef {(string | { phase: string })[][]} PostAuctionState * * @typedef {{ * collateralOffered?: Amount<'nat'>; @@ -1297,9 +1297,9 @@ export const prepareVaultManagerKit = ( E(auctionPF).getSchedules(), deposited, userSeatPromise, - E(liquidationRecorderKits.preAuctionRecorderKit.recorder).write( - preAuctionState, - ), + E( + liquidationRecorderKits.preAuctionRecorderKit.recorder, + ).writeFinal(preAuctionState), ]); const { storedCollateralQuote } = collateralEphemera( @@ -1341,21 +1341,21 @@ export const prepareVaultManagerKit = ( }; void E( liquidationRecorderKits.auctionResultRecorderKit.recorder, - ).write(auctionResultState); + ).writeFinal(auctionResultState); /** @type PostAuctionState */ const postAuctionState = plan.transfersToVault.map( ([id, transfer]) => [ `vault${vaultsInPlan[id].getVaultState().idInManager}`, { - transfer, + ...transfer, phase: vaultsInPlan[id].getVaultState().phase, }, ], ); void E( liquidationRecorderKits.postAuctionRecorderKit.recorder, - ).write(postAuctionState); + ).writeFinal(postAuctionState); } catch (err) { console.error('🚨 Error distributing proceeds:', err); } From e68046681aaf30580f5acc42fa519adcacb965da Mon Sep 17 00:00:00 2001 From: JorgeLopes-BytePitch Date: Thu, 25 Jan 2024 11:02:36 +0000 Subject: [PATCH 30/39] fix(liquidationVisibility): remove temporary changes made to tests --- .../vaultFactory/test-vaultLiquidation.js | 48 +------------------ 1 file changed, 1 insertion(+), 47 deletions(-) diff --git a/packages/inter-protocol/test/vaultFactory/test-vaultLiquidation.js b/packages/inter-protocol/test/vaultFactory/test-vaultLiquidation.js index d013af32393..e5c08655c52 100644 --- a/packages/inter-protocol/test/vaultFactory/test-vaultLiquidation.js +++ b/packages/inter-protocol/test/vaultFactory/test-vaultLiquidation.js @@ -248,7 +248,6 @@ const setupServices = async ( return { zoe, - space, timer, governor: g, vaultFactory: v, @@ -511,27 +510,6 @@ test('price drop', async t => { Fee: undefined, }, }); - - - const storage = await services.space.consume.chainStorage; - const node = 'vaultFactory.managers.manager0.liquidations.3600.auctionResult'; - - // @ts-ignore - const illustration = [...storage.keys()].sort().map( - /** @type {(k: string) => [string, unknown]} */ - key => [ - key.replace('mockChainStorageRoot.', 'published.'), - // @ts-ignore - storage.getBody(key), - ], - ); - - const pruned = illustration.filter( - node ? ([key, _]) => key.startsWith(`published.${node}`) : _entry => true, - ); - - console.log(pruned[0][1]); - }); test('price falls precipitously', async t => { @@ -2790,30 +2768,6 @@ test('refund to one of two loans', async t => { Fee: undefined, }, }); - - - const storage = await services.space.consume.chainStorage; - const node = 'vaultFactory.managers.manager0.liquidations.3600.vaults.'; - - // @ts-ignore - const illustration = [...storage.keys()].sort().map( - /** @type {(k: string) => [string, unknown]} */ - key => [ - key.replace('mockChainStorageRoot.', 'published.'), - // @ts-ignore - storage.getBody(key), - ], - ); - - const pruned = illustration.filter( - node ? ([key, _]) => key.startsWith(`published.${node}`) : _entry => true, - ); - - console.log('LOG: preAuction', pruned[1][1]); - - console.log('LOG: postAuction', pruned[0][1]); - - }); test('Bug 7784 reconstitute both', async t => { @@ -3467,4 +3421,4 @@ test('Bug 7851 & no bidders', async t => { }); await aethVaultMetrics.assertNoUpdate(); -}); +}); \ No newline at end of file From 34ecf5cc55c61ac1577ffa2ac5843024c8ad2e1e Mon Sep 17 00:00:00 2001 From: anilhelvaci Date: Thu, 25 Jan 2024 16:35:44 +0300 Subject: [PATCH 31/39] fix(liquidationVisibility): #4 test for scenario 2b passed, test api updated, #7 is fixed --- .../src/vaultFactory/vaultManager.js | 2 +- .../test-liquidationVisibility.js.md | 9 ++++++++ .../test-liquidationVisibility.js.snap | Bin 1437 -> 1502 bytes .../test-liquidationVisibility.js | 21 ++++++------------ .../vaultFactory/test-vaultLiquidation.js | 2 +- 5 files changed, 18 insertions(+), 16 deletions(-) diff --git a/packages/inter-protocol/src/vaultFactory/vaultManager.js b/packages/inter-protocol/src/vaultFactory/vaultManager.js index 3056733ce2f..40e049b76e7 100644 --- a/packages/inter-protocol/src/vaultFactory/vaultManager.js +++ b/packages/inter-protocol/src/vaultFactory/vaultManager.js @@ -1337,7 +1337,7 @@ export const prepareVaultManagerKit = ( mintedProceeds: plan.mintedProceeds, collateralSold: plan.collateralSold, collateralRemaining: plan.collatRemaining, - endTime: auctionSchedule.nextAuctionSchedule?.endTime, + endTime: auctionSchedule.liveAuctionSchedule?.endTime, }; void E( liquidationRecorderKits.auctionResultRecorderKit.recorder, diff --git a/packages/inter-protocol/test/liquidationVisibility/snapshots/test-liquidationVisibility.js.md b/packages/inter-protocol/test/liquidationVisibility/snapshots/test-liquidationVisibility.js.md index e3fe48cdcdf..d66565465dd 100644 --- a/packages/inter-protocol/test/liquidationVisibility/snapshots/test-liquidationVisibility.js.md +++ b/packages/inter-protocol/test/liquidationVisibility/snapshots/test-liquidationVisibility.js.md @@ -4,6 +4,15 @@ The actual snapshot is saved in `test-liquidationVisibility.js.snap`. Generated by [AVA](https://avajs.dev). +## liq-result-scenario-2 + +> Scenario 2 Liquidation Visibility Snapshot +> The example below illustrates the schema of the data published there. +> +> See also board marshalling conventions (_to appear_). + + [] + ## test liquidate vault with snapshot > Under "published", the "auction" node is delegated to the auctioneer contract. diff --git a/packages/inter-protocol/test/liquidationVisibility/snapshots/test-liquidationVisibility.js.snap b/packages/inter-protocol/test/liquidationVisibility/snapshots/test-liquidationVisibility.js.snap index cfe34852ff5d9fcbe84f49488d1abf5762f76a82..386cc36aaea9e9b2a879f5f9a1df016d84311d0e 100644 GIT binary patch literal 1502 zcmV<41tI!DRzV*Br)7Ps0T?j5f2`~gPNF_sEG$+l(QbxU_v|)4@46a6EFJj z>YkpS>S2LZ)l zK5n=cYq|~FbI)GXb>^|>x22W5;{T9>GE8z+lAUlIwoFLpsZyzMlhwJo^!`lfhmO-J zW1(fhfni&8)^LrwVH(~toeABm#~PgSB{ttQIfbpgNDb3$xt_y3?otn}uGZl5jM^u} zUw$mK*{Yj{+u*wY%HdR|-rfbu>%^aQ1GskT((=@D;RI{x%xBLKH`oNrLQ`T&9 z=FB}f7G_{+6kU1L3?t>V&5#VbXm}0UNkbwZFO)y%CWK^m;rAYB5;_9iDnklU6_QrS zAR)&CV?3Sne-rU|*?&y=F4N!Q@dqePdflaZ+yN{DSOZN+U|t5N033zRNZ@^PfC!)n zA_cdIHCrX?ql}NSmgeVjtZv(rgr!jyHFteu`@zNk@-ELqooK>R})|WUL@#nZrP) zp*Hl6jBn}1OA5&=q)f=?X(bxbux6VkYRqBgM4K5{FaA&}{-`@hRckp83U=BtG(N@K z+!QV8lEjspj;(R7yGNPfa$UU6%g|Z}Ysav#4yHr;Nt<;YBDx!~=qj$q9B*F~qU1~! z%_6S;Ayq@5PfsN=QRr_t)9o)G4plU#C1(|e8E^9$-{mo5p2vfpjX7Co@g;AP`+Jv; zan5+W>EnVs8{g{(4348+z;d-=xL|{y$W4~ zzJPv${*qk`Bzhakp_-wCISHi+R0nbjdQC=06Xlo^Ik}YU<|JEpn59dJ&$gM_3eruA zgw$fT->u5FAZ&k(*;XflV6Yrw*3P{w4IN)P1%ds>yLKYK903jf(3_8r#a3pT>#f&Kv_h4z~KumZfQZp1BF;v zq8%j|xDS%nlbr&qyR-2GWgZ*fQ4@}K#T=;~3e?#fr1$RJ%-zaOp|?2vt!TnD`AM$t zp=>`+Ix(A~Qt&S;N{hqUY3PlDR9b4H3NHcq5c*z5O9}KhAb&vX2PCwTKpzM41av}1 zhZ5)+Am^a>Wc1zydIiW;=yw_2kU$4jMX5r2R0-XbK%W8f9JDB-BNE!%x-~u#-3jYV z#MZtAknxtAJSsZ}>&M?Iqh0TU8e64eL9WKjl~s62Tg+Q=N63uVYPH+@Q4OnYc;c&3 z#PaKZ63g!0cM?l7Gn)qgjr<;1g_qloDZ8U428DuLfd574!k|=;gNfSx1juL5H5n}@ z&_97FMXcVUgjN&i7?54itc-3aQIekq)n)}@+MSF7~E4OBay*z zo6|$ldW{_<_^zHt1hb{y>&9@;btCW}{I7PdK`)iUi&ge@JiP^d16}vumZb_=lcGAWN^o}1rib*=D1Shg06XE|LgVrZ^9Zq#>x-? E00a5rp8x;= literal 1437 zcmV;O1!DR^RzVV;jvtE%00000000BEmraZmMHI)o=6ieRW5?w~c3Ejw4}N7cETEB#Sy&Jd!^+|s zNetF>7ds8T-9vTH>~g?_kQircGj}%B|2Y&B@W}(B-D9Kexc7}6$hLG-4rBcy| zu937xh6y?r{AiGXs*29!Ew-gn8-- zAlp=|pkkSWK<1z}^uCI3>BmbN$!nxc$QNlX8quifISy)!Q)i}4ZHLyyP%2{7U8HKZ zIA?A!$8D1xVr}NgmUKzs%1!Q>j9LB>YWvKRuk$Ljp0g!tySBSH*P);DXwM;{yA_MB z;s=xmd!i5(XS!^baSaZs+5raiRFV^g{zfys{_>rn%I37wv9-K0oJEoS@unrsWw_NSO_btYWSayn+2L(fPgpYI_Ypz$C`?> zwr_eZH;7tajkW4~9gNxIC}G)lA>MY#TgG00q}%pMtgR9*ICMJ4xb0aIxDf->MEd zoo+GL3?m*Y#KMy8D8bNun6$p^6lmRFh$krX^z^oxbhIPpNDok;e0P}M`*U;rPHqbQ z#UZw$8OLFZ%-Utpz4+3}*_4%n|5;gD49-qLZx@u((i2s98OX=b4=P$ppuYq86WTbW zpp^vr43KA`6Dm5AK+gg>4}GYj4<^uSK(0f7sOY8yI;?A2722gM=y(Es5y;EXvWiY9 zXn*V0_(b$3tUD1~2NpmkIMZZN*>S8Nu~SC7-bFpOO67uFi)Zrcv1*dk;szx^+Mbg0HGxh6*#Rx6=vV^fKvtmlRCM#8k68a^h{X17hN;e0 z_*wBjc2v~~nU+3&S=|SSUyI2vB3^GP-P7bI``s?hXG{Mg_RRLDYGDcv-pP%!2^Cl0 zgo+7+2dZQuGC1Zj<3O}tV;>TntLG5GLaG0{G1_pjY_3kz3wNmF|m3<3O??T@~ zH^tksQX%US6_QaBG;HB;$nB2t0{&2nKW0a|4d! z`ILSX4Qz!jH63Qazqf4Ij??l3ar*M5nYgemQNueS{}NYcBkEkd;%rLS=NbGvzGu`u r$}MAwa=(GG;w&ZlYU7H4o_*ud { await assertStorageData({ t, storageRoot: chainStorage, - path: `vaultFactory.managers.manager0.liquidations.${now1}.preAuction`, // now1 is the nominal start time + path: `vaultFactory.managers.manager0.liquidations.${now1.absValue.toString()}.vaults.preAuction`, // now1 is the nominal start time expected: [ [ 'vault0', @@ -411,25 +411,18 @@ test('liq-result-scenario-2', async t => { await assertStorageData({ t, storageRoot: chainStorage, - path: `vaultFactory.managers.manager0.liquidations.${now1}.postAuction`, // now1 is the nominal start time - expected: [ - [ - 'vault0', - { - collateral: aeth.makeEmpty(), - debt: run.makeEmpty(), - phase: Phase.LIQUIDATED, - }, - ], - ], + path: `vaultFactory.managers.manager0.liquidations.${now1.absValue.toString()}.vaults.postAuction`, // now1 is the nominal start time + expected: [], }); // FIXME: https://github.com/Jorge-Lopes/agoric-sdk-liquidation-visibility/issues/3#issuecomment-1905488335 await assertStorageData({ t, storageRoot: chainStorage, - path: `vaultFactory.managers.manager0.liquidations.${now1}.auctionResult`, // now1 is the nominal start time + path: `vaultFactory.managers.manager0.liquidations.${now1.absValue.toString()}.auctionResult`, // now1 is the nominal start time expected: { + collateralOffered: aeth.make(700n), + istTarget: run.make(5250n), collateralForReserve: aeth.makeEmpty(), shortfallToReserve: run.make(2065n), mintedProceeds: run.make(3185n), @@ -442,7 +435,7 @@ test('liq-result-scenario-2', async t => { // TODO: Snapshot here await documentStorageSchema(t, chainStorage, { note: 'Scenario 2 Liquidation Visibility Snapshot', - node: `vaultFactory.managers.manager0.liquidations.${now1}`, + node: `vaultFactory.managers.manager0.liquidations.${now1.toString()}`, }); }); diff --git a/packages/inter-protocol/test/vaultFactory/test-vaultLiquidation.js b/packages/inter-protocol/test/vaultFactory/test-vaultLiquidation.js index e5c08655c52..50d4030f733 100644 --- a/packages/inter-protocol/test/vaultFactory/test-vaultLiquidation.js +++ b/packages/inter-protocol/test/vaultFactory/test-vaultLiquidation.js @@ -3421,4 +3421,4 @@ test('Bug 7851 & no bidders', async t => { }); await aethVaultMetrics.assertNoUpdate(); -}); \ No newline at end of file +}); From 348707ee82f230d76b1151452954c20ce6604bdb Mon Sep 17 00:00:00 2001 From: anilhelvaci Date: Thu, 25 Jan 2024 18:12:28 +0300 Subject: [PATCH 32/39] chore(liquidationVisibility): #4 test for scenario 2a passed --- .../test-liquidationVisibility.js.md | 206 +++++++++++------- .../test-liquidationVisibility.js.snap | Bin 1502 -> 1542 bytes .../test-liquidationVisibility.js | 28 +-- 3 files changed, 138 insertions(+), 96 deletions(-) diff --git a/packages/inter-protocol/test/liquidationVisibility/snapshots/test-liquidationVisibility.js.md b/packages/inter-protocol/test/liquidationVisibility/snapshots/test-liquidationVisibility.js.md index d66565465dd..8394cbe6d06 100644 --- a/packages/inter-protocol/test/liquidationVisibility/snapshots/test-liquidationVisibility.js.md +++ b/packages/inter-protocol/test/liquidationVisibility/snapshots/test-liquidationVisibility.js.md @@ -11,107 +11,157 @@ Generated by [AVA](https://avajs.dev). > > See also board marshalling conventions (_to appear_). - [] + [ + [ + 'published.vaultFactory.managers.manager0.liquidations.3600.auctionResult', + { + collateralForReserve: { + brand: Object @Alleged: aEth brand {}, + value: 0n, + }, + collateralOffered: { + brand: Object @Alleged: aEth brand {}, + value: 700n, + }, + collateralRemaining: { + brand: Object @Alleged: aEth brand {}, + value: 0n, + }, + collateralSold: { + brand: Object @Alleged: aEth brand {}, + value: 700n, + }, + endTime: { + absValue: 3614n, + timerBrand: Object @Alleged: timerBrand {}, + }, + istTarget: { + brand: Object @Alleged: ZDEFAULT brand {}, + value: 5250n, + }, + mintedProceeds: { + brand: Object @Alleged: ZDEFAULT brand {}, + value: 3185n, + }, + shortfallToReserve: { + brand: Object @Alleged: ZDEFAULT brand {}, + value: 2065n, + }, + }, + ], + [ + 'published.vaultFactory.managers.manager0.liquidations.3600.vaults.postAuction', + [], + ], + [ + 'published.vaultFactory.managers.manager0.liquidations.3600.vaults.preAuction', + [ + [ + 'vault0', + { + collateralAmount: { + brand: Object @Alleged: aEth brand {}, + value: 700n, + }, + debtAmount: { + brand: Object @Alleged: ZDEFAULT brand {}, + value: 5250n, + }, + }, + ], + ], + ], + ] -## test liquidate vault with snapshot +## liq-result-scenario-3 -> Under "published", the "auction" node is delegated to the auctioneer contract. +> Scenario 3 Liquidation Visibility Snapshot > The example below illustrates the schema of the data published there. > > See also board marshalling conventions (_to appear_). [ [ - 'published.auction.book0', + 'published.vaultFactory.managers.manager0.liquidations.3600.auctionResult', { - collateralAvailable: { + collateralForReserve: { brand: Object @Alleged: aEth brand {}, - value: 0n, + value: 12n, }, - currentPriceLevel: null, - proceedsRaised: undefined, - remainingProceedsGoal: null, - startCollateral: { + collateralOffered: { brand: Object @Alleged: aEth brand {}, - value: 0n, + value: 63n, + }, + collateralRemaining: { + brand: Object @Alleged: aEth brand {}, + value: 5n, + }, + collateralSold: { + brand: Object @Alleged: aEth brand {}, + value: 8n, + }, + endTime: { + absValue: 3614n, + timerBrand: Object @Alleged: timerBrand {}, + }, + istTarget: { + brand: Object @Alleged: ZDEFAULT brand {}, + value: 258n, + }, + mintedProceeds: { + brand: Object @Alleged: ZDEFAULT brand {}, + value: 34n, + }, + shortfallToReserve: { + brand: Object @Alleged: ZDEFAULT brand {}, + value: 66n, }, - startPrice: null, - startProceedsGoal: null, }, ], [ - 'published.auction.governance', - { - current: { - AuctionStartDelay: { - type: 'relativeTime', - value: { - relValue: 10n, - timerBrand: Object @Alleged: timerBrand {}, + 'published.vaultFactory.managers.manager0.liquidations.3600.vaults.postAuction', + [ + [ + 'vault1', + { + Collateral: { + brand: Object @Alleged: aEth brand {}, + value: 43n, }, + phase: 'active', }, - ClockStep: { - type: 'relativeTime', - value: { - relValue: 2n, - timerBrand: Object @Alleged: timerBrand {}, + ], + ], + ], + [ + 'published.vaultFactory.managers.manager0.liquidations.3600.vaults.preAuction', + [ + [ + 'vault0', + { + collateralAmount: { + brand: Object @Alleged: aEth brand {}, + value: 15n, }, - }, - DiscountStep: { - type: 'nat', - value: 2000n, - }, - Electorate: { - type: 'invitation', - value: { - brand: Object @Alleged: Zoe Invitation brand {}, - value: [ - { - description: 'questionPoser', - handle: Object @Alleged: InvitationHandle {}, - installation: Object @Alleged: BundleInstallation {}, - instance: Object @Alleged: InstanceHandle {}, - }, - ], + debtAmount: { + brand: Object @Alleged: ZDEFAULT brand {}, + value: 100n, }, }, - LowestRate: { - type: 'nat', - value: 5500n, - }, - PriceLockPeriod: { - type: 'relativeTime', - value: { - relValue: 3n, - timerBrand: Object @Alleged: timerBrand {}, + ], + [ + 'vault1', + { + collateralAmount: { + brand: Object @Alleged: aEth brand {}, + value: 48n, }, - }, - StartFrequency: { - type: 'relativeTime', - value: { - relValue: 3600n, - timerBrand: Object @Alleged: timerBrand {}, + debtAmount: { + brand: Object @Alleged: ZDEFAULT brand {}, + value: 158n, }, }, - StartingRate: { - type: 'nat', - value: 10500n, - }, - }, - }, - ], - [ - 'published.auction.schedule', - { - activeStartTime: null, - nextDescendingStepTime: { - absValue: 7210n, - timerBrand: Object @Alleged: timerBrand {}, - }, - nextStartTime: { - absValue: 7210n, - timerBrand: Object @Alleged: timerBrand {}, - }, - }, + ], + ], ], ] diff --git a/packages/inter-protocol/test/liquidationVisibility/snapshots/test-liquidationVisibility.js.snap b/packages/inter-protocol/test/liquidationVisibility/snapshots/test-liquidationVisibility.js.snap index 386cc36aaea9e9b2a879f5f9a1df016d84311d0e..514bb3fe77913462b80c828c4d6c854c6b4310e8 100644 GIT binary patch literal 1542 zcmV+h2Ko6xRzV|6XIMEXLv}F)upo$W+1=?NtY7S6?yrg5 zh8{)ikspf)00000000B+ms@BYRUF3m>}0c-rrm6kHf@bMzKEh-cC$%ZszK6A6q_1t zV#Q!7XLnDsV`q1^GqbTl@ct0`Pztp^6d!B_qtI5ULJ>`UkUof_K2)IyK1fmepcO1d zYZ1Shy`4Fe&1Os=R1Eoc&+PgA&;QK1%{Rv<)2f!)XS{TRnTn~hlB(?ApfjVSnj4G^ zD^Ok0HuNe+ffkLNW*RS^$Y!ZYpA5e)`GP*{kAz4|AHFw3+n`aXleET2U`W^L3?bE0 zG#d4hh>u7~KIUtv1LiLOudkz2&ZqrmECrI#q`?9n-?}X=YPDa#XBNjZRK7on>vLqos~?){$K- zPnCjFn6jZk4q8=*#x%9uNRF!yvO;!T$(K8DgsTWEhB;34DQ4PL=lC}BO2K5=M|Ca3 zSk|!XFLU)B*r>WWNmX@RtKDCuy2ZKzl}#%RqO=v72nh4gBFqDkW`H~oy(XfqF7zyr zdFX=rtfWEG#IX zt6XS5ko%xrLFeM^6c?x8p1M83`qTx|IxS$8M6F#~`*E78C1zidHbN3y?5wv~X=8$q zKD3O?um1ha4lG2|#%`W$Nev|X>=ehi6cH1fMBh@*Q|rWeZ=GoA@9pRB;i|{*RB}^4 z7k=GicyD5$f8k@e)jmrT_WtyRDhH@@nOO(UkgBpNmfbAV?PgB4Gumqo{{iQx5FFuy z>m$jZvQ@M#M!IY0#J26jLywP)J9D*FKe5*DL2r}LSV-6wUCwP$8#FCcAGG%R;W(7n zN`>-KcZ?$@q1Ver>1 zGRXqEcF}oaDor!>z}#5H!O6YyNc{mW?^X;Yt*DAQBac;#U1U7RWOjh&iz<^5t34wt zs#-Ek9TQ~8CTfjL4$nDRo3yVi7bw^4gY}cn;*rQ0V=`3@O-^f6&&qkK8#z3Q@NASb zT49ja+7Y4`U?=tQLYr-q}!v$f%HK;MAYq3_W{`t%{KR_Zv#0EoewVd zE)4K10N+4A31GJf{s!PrXm!X5-ny{sl|!DMm;Pig7ret`@IZp-uyG8k!Q?Q@X^*1p!pk)~~&Yt#mA{4(mUK&>l;8}D?D(Val?5Iv1J&qChW* z=q)bvIFLE$oQU4;LcarY3Hn1s2ZT`^}$M sb6!(hr=74TyO#b|wSMVnPxGC=-u~=yy^N*)(Jy0v0n}6F0TLJh09m#3$^ZZW literal 1502 zcmV<41tI!DRzV*Br)7Ps0T?j5f2`~gPNF_sEG$+l(QbxU_v|)4@46a6EFJj z>YkpS>S2LZ)l zK5n=cYq|~FbI)GXb>^|>x22W5;{T9>GE8z+lAUlIwoFLpsZyzMlhwJo^!`lfhmO-J zW1(fhfni&8)^LrwVH(~toeABm#~PgSB{ttQIfbpgNDb3$xt_y3?otn}uGZl5jM^u} zUw$mK*{Yj{+u*wY%HdR|-rfbu>%^aQ1GskT((=@D;RI{x%xBLKH`oNrLQ`T&9 z=FB}f7G_{+6kU1L3?t>V&5#VbXm}0UNkbwZFO)y%CWK^m;rAYB5;_9iDnklU6_QrS zAR)&CV?3Sne-rU|*?&y=F4N!Q@dqePdflaZ+yN{DSOZN+U|t5N033zRNZ@^PfC!)n zA_cdIHCrX?ql}NSmgeVjtZv(rgr!jyHFteu`@zNk@-ELqooK>R})|WUL@#nZrP) zp*Hl6jBn}1OA5&=q)f=?X(bxbux6VkYRqBgM4K5{FaA&}{-`@hRckp83U=BtG(N@K z+!QV8lEjspj;(R7yGNPfa$UU6%g|Z}Ysav#4yHr;Nt<;YBDx!~=qj$q9B*F~qU1~! z%_6S;Ayq@5PfsN=QRr_t)9o)G4plU#C1(|e8E^9$-{mo5p2vfpjX7Co@g;AP`+Jv; zan5+W>EnVs8{g{(4348+z;d-=xL|{y$W4~ zzJPv${*qk`Bzhakp_-wCISHi+R0nbjdQC=06Xlo^Ik}YU<|JEpn59dJ&$gM_3eruA zgw$fT->u5FAZ&k(*;XflV6Yrw*3P{w4IN)P1%ds>yLKYK903jf(3_8r#a3pT>#f&Kv_h4z~KumZfQZp1BF;v zq8%j|xDS%nlbr&qyR-2GWgZ*fQ4@}K#T=;~3e?#fr1$RJ%-zaOp|?2vt!TnD`AM$t zp=>`+Ix(A~Qt&S;N{hqUY3PlDR9b4H3NHcq5c*z5O9}KhAb&vX2PCwTKpzM41av}1 zhZ5)+Am^a>Wc1zydIiW;=yw_2kU$4jMX5r2R0-XbK%W8f9JDB-BNE!%x-~u#-3jYV z#MZtAknxtAJSsZ}>&M?Iqh0TU8e64eL9WKjl~s62Tg+Q=N63uVYPH+@Q4OnYc;c&3 z#PaKZ63g!0cM?l7Gn)qgjr<;1g_qloDZ8U428DuLfd574!k|=;gNfSx1juL5H5n}@ z&_97FMXcVUgjN&i7?54itc-3aQIekq)n)}@+MSF7~E4OBay*z zo6|$ldW{_<_^zHt1hb{y>&9@;btCW}{I7PdK`)iUi&ge@JiP^d16}vumZb_=lcGAWN^o}1rib*=D1Shg06XE|LgVrZ^9Zq#>x-? E00a5rp8x;= diff --git a/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js b/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js index 532442152cd..0a6d709c47e 100644 --- a/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js +++ b/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js @@ -435,7 +435,7 @@ test('liq-result-scenario-2', async t => { // TODO: Snapshot here await documentStorageSchema(t, chainStorage, { note: 'Scenario 2 Liquidation Visibility Snapshot', - node: `vaultFactory.managers.manager0.liquidations.${now1.toString()}`, + node: `vaultFactory.managers.manager0.liquidations.${now1.absValue.toString()}`, }); }); @@ -456,7 +456,6 @@ test('liq-result-scenario-3', async t => { aeth.make(1n), manualTimer, ONE_WEEK, - 500n, { StartFrequency: ONE_HOUR }, ); @@ -583,7 +582,7 @@ test('liq-result-scenario-3', async t => { await assertStorageData({ t, storageRoot: chainStorage, - path: `vaultFactory.managers.manager0.liquidations.${time}.preAuction`, // time is the nominal start time + path: `vaultFactory.managers.manager0.liquidations.${time.absValue.toString()}.vaults.preAuction`, // time is the nominal start time expected: [ [ 'vault0', // Alice's vault @@ -595,8 +594,8 @@ test('liq-result-scenario-3', async t => { [ 'vault1', // Bob's vault { - collateral: bobCollateralAmount, - debt: await E(bobVault).getCurrentDebt(), + collateralAmount: bobCollateralAmount, + debtAmount: await E(bobVault).getCurrentDebt(), }, ], ], @@ -672,21 +671,12 @@ test('liq-result-scenario-3', async t => { await assertStorageData({ t, storageRoot: chainStorage, - path: `vaultFactory.managers.manager0.liquidations.${time}.postAuction`, // time is the nominal start time + path: `vaultFactory.managers.manager0.liquidations.${time.absValue.toString()}.vaults.postAuction`, // time is the nominal start time expected: [ - [ - 'vault0', // Alice got liquidated - { - collateral: aeth.makeEmpty(), - debt: run.makeEmpty(), - phase: Phase.LIQUIDATED, - }, - ], [ 'vault1', // Bob got reinstated { - collateral: recoveredBobCollateral, - debt: run.make(158n), + Collateral: recoveredBobCollateral, phase: Phase.ACTIVE, }, ], @@ -697,8 +687,10 @@ test('liq-result-scenario-3', async t => { await assertStorageData({ t, storageRoot: chainStorage, - path: `vaultFactory.managers.manager0.liquidations.${time}.auctionResult`, // now1 is the nominal start time + path: `vaultFactory.managers.manager0.liquidations.${time.absValue.toString()}.auctionResult`, // now1 is the nominal start time expected: { + collateralOffered: aeth.make(63n), + istTarget: run.make(258n), collateralForReserve: aeth.make(12n), shortfallToReserve: run.make(66n), mintedProceeds: run.make(34n), @@ -711,6 +703,6 @@ test('liq-result-scenario-3', async t => { // TODO: Snapshot here await documentStorageSchema(t, chainStorage, { note: 'Scenario 3 Liquidation Visibility Snapshot', - node: `vaultFactory.managers.manager0.liquidations.${time}`, + node: `vaultFactory.managers.manager0.liquidations.${time.absValue.toString()}`, }); }); From e7259fbb2833ece6c0e8e3553aaec57851065436 Mon Sep 17 00:00:00 2001 From: alexanderem49 Date: Fri, 26 Jan 2024 11:55:24 +0400 Subject: [PATCH 33/39] chore(liquidationVisibility): #4 update scenario 1 --- .../test-liquidationVisibility.js.md | 69 +++++++++++++ .../test-liquidationVisibility.js.snap | Bin 1542 -> 1674 bytes .../test-liquidationVisibility.js | 93 +++++++++++++++++- 3 files changed, 158 insertions(+), 4 deletions(-) diff --git a/packages/inter-protocol/test/liquidationVisibility/snapshots/test-liquidationVisibility.js.md b/packages/inter-protocol/test/liquidationVisibility/snapshots/test-liquidationVisibility.js.md index 8394cbe6d06..fa9e2107b77 100644 --- a/packages/inter-protocol/test/liquidationVisibility/snapshots/test-liquidationVisibility.js.md +++ b/packages/inter-protocol/test/liquidationVisibility/snapshots/test-liquidationVisibility.js.md @@ -4,6 +4,75 @@ The actual snapshot is saved in `test-liquidationVisibility.js.snap`. Generated by [AVA](https://avajs.dev). +## liq-result-scenario-1 + +> Scenario 1 Liquidation Visibility Snapshot +> The example below illustrates the schema of the data published there. +> +> See also board marshalling conventions (_to appear_). + + [ + [ + 'published.vaultFactory.managers.manager0.liquidations.3600.auctionResult', + { + collateralForReserve: { + brand: Object @Alleged: aEth brand {}, + value: 0n, + }, + collateralOffered: { + brand: Object @Alleged: aEth brand {}, + value: 400n, + }, + collateralRemaining: { + brand: Object @Alleged: aEth brand {}, + value: 0n, + }, + collateralSold: { + brand: Object @Alleged: aEth brand {}, + value: 400n, + }, + endTime: { + absValue: 3614n, + timerBrand: Object @Alleged: timerBrand {}, + }, + istTarget: { + brand: Object @Alleged: ZDEFAULT brand {}, + value: 1680n, + }, + mintedProceeds: { + brand: Object @Alleged: ZDEFAULT brand {}, + value: 1680n, + }, + shortfallToReserve: { + brand: Object @Alleged: ZDEFAULT brand {}, + value: 0n, + }, + }, + ], + [ + 'published.vaultFactory.managers.manager0.liquidations.3600.vaults.postAuction', + [], + ], + [ + 'published.vaultFactory.managers.manager0.liquidations.3600.vaults.preAuction', + [ + [ + 'vault0', + { + collateralAmount: { + brand: Object @Alleged: aEth brand {}, + value: 400n, + }, + debtAmount: { + brand: Object @Alleged: ZDEFAULT brand {}, + value: 1680n, + }, + }, + ], + ], + ], + ] + ## liq-result-scenario-2 > Scenario 2 Liquidation Visibility Snapshot diff --git a/packages/inter-protocol/test/liquidationVisibility/snapshots/test-liquidationVisibility.js.snap b/packages/inter-protocol/test/liquidationVisibility/snapshots/test-liquidationVisibility.js.snap index 514bb3fe77913462b80c828c4d6c854c6b4310e8..bf87f7d5f971b35c0413aa2d82265cb840149df8 100644 GIT binary patch literal 1674 zcmV;526g#CRzVhIfM+p7F_OIzWegxa5T}RNl7p@R zq2z>|xF3rM00000000B+mwkv7WgN$Mc5h!jz1zF#w!YG%0yYsL_&bK19Gw(7w z5_xv_x!cj%opomR@(6mtg2O-zGfDcRGsprput1_TFodG0{;)uTf{?;L$|A8ci@r1a zHuKEw&Wm(syT4FOiGq@)naCKy$gCr2Rg%XeIg%%%R8}g5Xau8I6fi)7 zlvg5&-e@#JifQfJW_?KYhwOHjo_DHD8>f*;4^ScnNm2VtN%eHw8l>BB1JcRVnC*#j zwg4A=6~)(~PoYatmj|;6ZG^JWgr__YzJb(snpQbawby{Mg11^S0H~t?LGz#SkM@dWzb_R8nmFhfIJBuV9{CDz5+P~onz727W4{`YZ~Tf z&^Z<~4x|qn@S7KBC%ZV^`qT~kYg6Y<$s~_e(x&aw>OaSbSfu(lu9f4WhS=wv#d?xa zWAyXW$o#_fXEtF0l2W!9$(BTKEUBkBXh;!Ku}S1rr99P6oN?BPmRN6}@fm)ZUifd$ zhi|Gg*ry+)QGIj%m`Pu8kgUQeelIQ)9epG9`wN^f{(3b>Ut_ZX#!0d@G^PZ@Vm>8&ySbObc?9WNXeCeD! zXdprvno=uuNOuNxA3@);>E>FtpeyKd`Jlx?2AyB0XDfr#*+|=+MVA*6Gx+lwOfrKm zXgE()Bq=KHIx<+cZ+wj3P`kbPErKGX1W{1O`N6WQ3k_u{Pj``AL8LrlrN?nvhW{fDcF{1nG8PWIZ8`1A=jA+=~tP#DpWJF6#1K+8) zvMKj^EAv?2Osi$5QFj2@2~F60)Hi?}htB#Ny$b{U0>D?$j|@1U1%C(dH#9e31{Y85 zdij8(`_dhY8-mw64DL-Bf**1g%o#1;BMxKxoO<4^0Xxq-;j9aucu$`p`lQ2=OC$}! zr<@LZZzH=K)A#y^f%-Ru(H8$?%az(&7`&P6^BVN_0ceCZsU4Oby9dZV=md){u%Mp- zIR{-~(Oc_S-;R)B)vc~x6zuF4vP7ZPLC1A1Xa{<=l@%d7Yo86(vG&zA_S?2`x?UT2 z@QbLs1o>t$58i1RqdS1y4XtL;g%&goM1Y=V(OWF&B#y%|k9?YHr~I^s07cG@v}va9*GszuGC-S#_uk^b3j Uc^OOmr(eeY0Y~|*K#L*(03@?Cv;Y7A literal 1542 zcmV+h2Ko6xRzV|6XIMEXLv}F)upo$W+1=?NtY7S6?yrg5 zh8{)ikspf)00000000B+ms@BYRUF3m>}0c-rrm6kHf@bMzKEh-cC$%ZszK6A6q_1t zV#Q!7XLnDsV`q1^GqbTl@ct0`Pztp^6d!B_qtI5ULJ>`UkUof_K2)IyK1fmepcO1d zYZ1Shy`4Fe&1Os=R1Eoc&+PgA&;QK1%{Rv<)2f!)XS{TRnTn~hlB(?ApfjVSnj4G^ zD^Ok0HuNe+ffkLNW*RS^$Y!ZYpA5e)`GP*{kAz4|AHFw3+n`aXleET2U`W^L3?bE0 zG#d4hh>u7~KIUtv1LiLOudkz2&ZqrmECrI#q`?9n-?}X=YPDa#XBNjZRK7on>vLqos~?){$K- zPnCjFn6jZk4q8=*#x%9uNRF!yvO;!T$(K8DgsTWEhB;34DQ4PL=lC}BO2K5=M|Ca3 zSk|!XFLU)B*r>WWNmX@RtKDCuy2ZKzl}#%RqO=v72nh4gBFqDkW`H~oy(XfqF7zyr zdFX=rtfWEG#IX zt6XS5ko%xrLFeM^6c?x8p1M83`qTx|IxS$8M6F#~`*E78C1zidHbN3y?5wv~X=8$q zKD3O?um1ha4lG2|#%`W$Nev|X>=ehi6cH1fMBh@*Q|rWeZ=GoA@9pRB;i|{*RB}^4 z7k=GicyD5$f8k@e)jmrT_WtyRDhH@@nOO(UkgBpNmfbAV?PgB4Gumqo{{iQx5FFuy z>m$jZvQ@M#M!IY0#J26jLywP)J9D*FKe5*DL2r}LSV-6wUCwP$8#FCcAGG%R;W(7n zN`>-KcZ?$@q1Ver>1 zGRXqEcF}oaDor!>z}#5H!O6YyNc{mW?^X;Yt*DAQBac;#U1U7RWOjh&iz<^5t34wt zs#-Ek9TQ~8CTfjL4$nDRo3yVi7bw^4gY}cn;*rQ0V=`3@O-^f6&&qkK8#z3Q@NASb zT49ja+7Y4`U?=tQLYr-q}!v$f%HK;MAYq3_W{`t%{KR_Zv#0EoewVd zE)4K10N+4A31GJf{s!PrXm!X5-ny{sl|!DMm;Pig7ret`@IZp-uyG8k!Q?Q@X^*1p!pk)~~&Yt#mA{4(mUK&>l;8}D?D(Val?5Iv1J&qChW* z=q)bvIFLE$oQU4;LcarY3Hn1s2ZT`^}$M sb6!(hr=74TyO#b|wSMVnPxGC=-u~=yy^N*)(Jy0v0n}6F0TLJh09m#3$^ZZW diff --git a/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js b/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js index 0a6d709c47e..4963cfb2c31 100644 --- a/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js +++ b/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js @@ -53,6 +53,7 @@ import { assertVaultNotification, } from './assertions.js'; import { Phase } from '../vaultFactory/driver.js'; +import { TimeMath } from '@agoric/time'; const trace = makeTracer('TestLiquidationVisibility', false); @@ -113,9 +114,6 @@ test('liq-result-scenario-1', async t => { chainStorage, } = services; - const storageData = await getDataFromVstorage(chainStorage, 'whatever'); - t.log(storageData); - const { reserveTracker } = await getMetricTrackers({ t, collateralManager: aethCollateralManager, @@ -154,17 +152,46 @@ test('liq-result-scenario-1', async t => { await assertMintedAmount(t, vaultSeat, wantMinted); await assertVaultCollateral(t, vault, 400n); + // Check that no child node with auction start time's name created before the liquidation + const vstorageBeforeLiquidation = await getDataFromVstorage( + chainStorage, + `vaultFactory.managers.manager0.liquidations`, + ); + t.is(vstorageBeforeLiquidation.length, 0); + // drop collateral price from 5:1 to 4:1 and liquidate vault aethTestPriceAuthority.setPrice(makeRatio(40n, run.brand, 10n, aeth.brand)); await assertVaultState(t, vaultNotifier, 'active'); - const { startTime, time } = await startAuctionClock( + const { startTime, time, endTime } = await startAuctionClock( auctioneerKit, manualTimer, ); let currentTime = time; + // Check that {timestamp}.vaults.preAuction values are correct before auction is completed + const vstorageDuringLiquidation = await getDataFromVstorage( + chainStorage, + `vaultFactory.managers.manager0.liquidations`, + ); + t.not(vstorageDuringLiquidation.length, 0); + const debtDuringLiquidation = await E(vault).getCurrentDebt(); + await assertStorageData({ + t, + storageRoot: chainStorage, + path: `vaultFactory.managers.manager0.liquidations.${time.absValue.toString()}.vaults.preAuction`, + expected: [ + [ + "vault0", + { + collateralAmount: collateralAmount, + debtAmount: debtDuringLiquidation + }, + ] + ] + }); + await assertVaultState(t, vaultNotifier, 'liquidating'); await assertVaultCollateral(t, vault, 0n); await assertVaultCurrentDebt(t, vault, wantMinted); @@ -192,6 +219,64 @@ test('liq-result-scenario-1', async t => { }, }; await assertReserveState(reserveTracker, 'like', expectedReserveState); + + // Check that {timestamp}.vaults.postAuction values are correct after auction is completed + await assertStorageData({ + t, + storageRoot: chainStorage, + path: `vaultFactory.managers.manager0.liquidations.${time.absValue.toString()}.vaults.preAuction`, + expected: [ + [ + "vault0", + { + collateralAmount: collateralAmount, + debtAmount: debtDuringLiquidation + }, + ] + ] + }); + + // TODO: postAuction is not filled yet + // should be empty + // await assertStorageData({ + // t, + // storageRoot: chainStorage, + // path: `vaultFactory.managers.manager0.liquidations.${time.absValue.toString()}.vaults.postAuction`, + // expected: [ + // [ + // "vault0", + // { + // collateralAmount: collateralAmount, + // debtAmount: debtDuringLiquidation, + // phase: Phase.LIQUIDATED, + // }, + // ] + // ] + // }); + + + // Check that {timestamp}.auctionResult values are correct after auction is completed + await assertStorageData({ + t, + storageRoot: chainStorage, + path: `vaultFactory.managers.manager0.liquidations.${time.absValue.toString()}.auctionResult`, + expected: { + collateralOffered: collateralAmount, + istTarget: run.make(1680n), + collateralForReserve: aeth.makeEmpty(), + shortfallToReserve: run.makeEmpty(), + mintedProceeds: run.make(1680n), + collateralSold: aeth.make(400n), + collateralRemaining: aeth.makeEmpty(), + endTime: endTime, + }, + }); + + // Create snapshot of the storage node + await documentStorageSchema(t, chainStorage, { + note: 'Scenario 1 Liquidation Visibility Snapshot', + node: `vaultFactory.managers.manager0.liquidations.${time.absValue.toString()}`, + }); }); // We'll make a loan, and trigger liquidation via price changes. The interest From 4c45f2a7730ebb05a2a4fa355debf82cb00c57a6 Mon Sep 17 00:00:00 2001 From: anilhelvaci Date: Tue, 30 Jan 2024 10:30:02 +0300 Subject: [PATCH 34/39] fix(liquidationVisibility): add pattern matcher to `getVaultState` --- packages/inter-protocol/src/vaultFactory/vault.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/inter-protocol/src/vaultFactory/vault.js b/packages/inter-protocol/src/vaultFactory/vault.js index 0a5fe3ec4b8..9c9602e6da7 100644 --- a/packages/inter-protocol/src/vaultFactory/vault.js +++ b/packages/inter-protocol/src/vaultFactory/vault.js @@ -131,7 +131,9 @@ export const VaultI = M.interface('Vault', { getCurrentDebt: M.call().returns(AmountShape), getNormalizedDebt: M.call().returns(AmountShape), getVaultSeat: M.call().returns(SeatShape), - getVaultState: M.call().returns(M.any()), + getVaultState: M.call().returns( + harden({ idInManager: M.string(), phase: M.string() }), + ), initVaultKit: M.call(SeatShape, StorageNodeShape).returns(M.promise()), liquidated: M.call().returns(undefined), liquidating: M.call().returns(undefined), From 683f56d0bd474483e74b4ac8709493c90c07d274 Mon Sep 17 00:00:00 2001 From: anilhelvaci Date: Tue, 30 Jan 2024 14:31:13 +0300 Subject: [PATCH 35/39] feat(liquidationVisibility): add LiquidationVisibilityWriters to improve readability, fetch schedule during the auction itself --- .../src/vaultFactory/liquidation.js | 14 +- .../inter-protocol/src/vaultFactory/types.js | 25 ++++ .../src/vaultFactory/vaultManager.js | 137 ++++++++++++------ 3 files changed, 127 insertions(+), 49 deletions(-) diff --git a/packages/inter-protocol/src/vaultFactory/liquidation.js b/packages/inter-protocol/src/vaultFactory/liquidation.js index f4863cf5713..18bfb07d4d8 100644 --- a/packages/inter-protocol/src/vaultFactory/liquidation.js +++ b/packages/inter-protocol/src/vaultFactory/liquidation.js @@ -18,6 +18,13 @@ const trace = makeTracer('LIQ'); /** @typedef {import('@agoric/time').CancelToken} CancelToken */ /** @typedef {import('@agoric/time').RelativeTimeRecord} RelativeTimeRecord */ +/** + * @typedef {MapStore< + * Vault, + * { collateralAmount: Amount<'nat'>; debtAmount: Amount<'nat'> } + * >} VaultData + */ + const makeCancelToken = makeCancelTokenMaker('liq'); /** @@ -269,12 +276,7 @@ export const getLiquidatableVaults = ( const vaultsToLiquidate = prioritizedVaults.removeVaultsBelow( collateralizationDetails, ); - /** - * @type {MapStore< - * Vault, - * { collateralAmount: Amount<'nat'>; debtAmount: Amount<'nat'> } - * >} - */ + /** @type {VaultData} */ const vaultData = makeScalarMapStore(); const { zcfSeat: liqSeat } = zcf.makeEmptySeatKit(); diff --git a/packages/inter-protocol/src/vaultFactory/types.js b/packages/inter-protocol/src/vaultFactory/types.js index 01c3750b9ad..c438c16ceb3 100644 --- a/packages/inter-protocol/src/vaultFactory/types.js +++ b/packages/inter-protocol/src/vaultFactory/types.js @@ -21,6 +21,8 @@ * * @typedef {import('@agoric/time').Timestamp} Timestamp * + * @typedef {import('@agoric/time').TimestampRecord} TimestampRecord + * * @typedef {import('@agoric/time').RelativeTime} RelativeTime */ @@ -142,3 +144,26 @@ */ /** @typedef {{ key: 'governedParams' | { collateralBrand: Brand } }} VaultFactoryParamPath */ + +/** + * @typedef {{ + * plan: import('./proceeds.js').DistributionPlan; + * vaultsInPlan: Array; + * }} PostAuctionParams + * + * @typedef {{ + * plan: import('./proceeds.js').DistributionPlan; + * totalCollateral: Amount<'nat'>; + * totalDebt: Amount<'nat'>; + * auctionSchedule: import('../auction/scheduler.js').FullSchedule; + * }} AuctionResultsParams + */ + +/** + * @typedef {import('./liquidation.js').VaultData} VaultData + * + * @typedef {object} LiquidationVisibilityWriters + * @property {(vaultData: VaultData) => Promise} writePreAuction + * @property {(postAuctionParams: PostAuctionParams) => Promise} writePostAuction + * @property {(auctionResultParams: AuctionResultsParams) => Promise} writeAuctionResults + */ diff --git a/packages/inter-protocol/src/vaultFactory/vaultManager.js b/packages/inter-protocol/src/vaultFactory/vaultManager.js index 40e049b76e7..d36b4e65327 100644 --- a/packages/inter-protocol/src/vaultFactory/vaultManager.js +++ b/packages/inter-protocol/src/vaultFactory/vaultManager.js @@ -233,6 +233,9 @@ export const watchQuoteNotifier = async (notifierP, watcher, ...args) => { * auctionResultRecorderKit: import('@agoric/zoe/src/contractSupport/recorder.js').RecorderKit; * }} LiquidationRecorderKits */ + +/** @typedef {import('./liquidation.js').VaultData} VaultData */ + // any b/c will be filled after start() const collateralEphemera = makeEphemeraProvider(() => /** @type {any} */ ({})); @@ -684,7 +687,81 @@ export const prepareVaultManagerKit = ( }, /** - * @param {{ absValue: any }} timestamp + * @param {TimestampRecord} timestamp + * @returns {Promise} + */ + async makeLiquidationVisibilityWriters(timestamp) { + const liquidationRecorderKits = + await this.facets.helper.makeLiquidationRecorderKits(timestamp); + + /** @param {VaultData} vaultData */ + const writePreAuction = vaultData => { + /** @type PreAuctionState */ + const preAuctionState = [...vaultData.entries()].map( + ([vault, data]) => [ + `vault${vault.getVaultState().idInManager}`, + { ...data }, + ], + ); + + return E( + liquidationRecorderKits.preAuctionRecorderKit.recorder, + ).writeFinal(preAuctionState); + }; + + /** + * @param {PostAuctionParams} params + * @returns {Promise} + */ + const writePostAuction = ({ plan, vaultsInPlan }) => { + /** @type PostAuctionState */ + const postAuctionState = plan.transfersToVault.map( + ([id, transfer]) => [ + `vault${vaultsInPlan[id].getVaultState().idInManager}`, + { + ...transfer, + phase: vaultsInPlan[id].getVaultState().phase, + }, + ], + ); + return E( + liquidationRecorderKits.postAuctionRecorderKit.recorder, + ).writeFinal(postAuctionState); + }; + + /** @param {AuctionResultsParams} params */ + const writeAuctionResults = ({ + plan, + totalCollateral, + totalDebt, + auctionSchedule, + }) => { + /** @type AuctionResultState */ + const auctionResultState = { + collateralOffered: totalCollateral, + istTarget: totalDebt, + collateralForReserve: plan.collateralForReserve, + shortfallToReserve: plan.shortfallToReserve, + mintedProceeds: plan.mintedProceeds, + collateralSold: plan.collateralSold, + collateralRemaining: plan.collatRemaining, + // @ts-expect-error + endTime: auctionSchedule.liveAuctionSchedule.endTime, // write if nor rejected + }; + return E( + liquidationRecorderKits.auctionResultRecorderKit.recorder, + ).writeFinal(auctionResultState); + }; + + return harden({ + writePreAuction, + writePostAuction, + writeAuctionResults, + }); + }, + + /** + * @param {TimestampRecord} timestamp * @returns {Promise} */ async makeLiquidationRecorderKits(timestamp) { @@ -1193,7 +1270,7 @@ export const prepareVaultManagerKit = ( }, /** * @param {ERef} auctionPF - * @param {{ absValue: bigint }} timestamp + * @param {TimestampRecord} timestamp */ async liquidateVaults(auctionPF, timestamp) { const { state, facets } = this; @@ -1258,6 +1335,7 @@ export const prepareVaultManagerKit = ( liquidatingVaults.getSize(), totalCollateral, ); + const schedulesP = E(auctionPF).getSchedules(); helper.markLiquidating(totalDebt, totalCollateral); void helper.writeMetrics(); @@ -1276,30 +1354,21 @@ export const prepareVaultManagerKit = ( ), ); - const [{ userSeatPromise, deposited }, liquidationRecorderKits] = + const [{ userSeatPromise, deposited }, liquidationVisibilityWriters] = await Promise.all([ makeDeposit, - helper.makeLiquidationRecorderKits(timestamp), + helper.makeLiquidationVisibilityWriters(timestamp), ]); - /** @type PreAuctionState */ - const preAuctionState = [...vaultData.entries()].map( - ([vault, data]) => [ - `vault${vault.getVaultState().idInManager}`, - { ...data }, - ], - ); + void liquidationVisibilityWriters.writePreAuction(vaultData); // This is expected to wait for the duration of the auction, which // is controlled by the auction parameters startFrequency, clockStep, // and the difference between startingRate and lowestRate. const [auctionSchedule, proceeds] = await Promise.all([ - E(auctionPF).getSchedules(), + schedulesP, deposited, userSeatPromise, - E( - liquidationRecorderKits.preAuctionRecorderKit.recorder, - ).writeFinal(preAuctionState), ]); const { storedCollateralQuote } = collateralEphemera( @@ -1328,34 +1397,16 @@ export const prepareVaultManagerKit = ( vaultsInPlan, }); - /** @type AuctionResultState */ - const auctionResultState = { - collateralOffered: totalCollateral, - istTarget: totalDebt, - collateralForReserve: plan.collateralForReserve, - shortfallToReserve: plan.shortfallToReserve, - mintedProceeds: plan.mintedProceeds, - collateralSold: plan.collateralSold, - collateralRemaining: plan.collatRemaining, - endTime: auctionSchedule.liveAuctionSchedule?.endTime, - }; - void E( - liquidationRecorderKits.auctionResultRecorderKit.recorder, - ).writeFinal(auctionResultState); - - /** @type PostAuctionState */ - const postAuctionState = plan.transfersToVault.map( - ([id, transfer]) => [ - `vault${vaultsInPlan[id].getVaultState().idInManager}`, - { - ...transfer, - phase: vaultsInPlan[id].getVaultState().phase, - }, - ], - ); - void E( - liquidationRecorderKits.postAuctionRecorderKit.recorder, - ).writeFinal(postAuctionState); + void liquidationVisibilityWriters.writeAuctionResults({ + plan, + totalCollateral, + totalDebt, + auctionSchedule, + }); + void liquidationVisibilityWriters.writePostAuction({ + plan, + vaultsInPlan, + }); } catch (err) { console.error('🚨 Error distributing proceeds:', err); } From 732e1d7e7794937c2a9d4b5aff1f2d61b3d0ba92 Mon Sep 17 00:00:00 2001 From: anilhelvaci Date: Wed, 31 Jan 2024 11:37:45 +0300 Subject: [PATCH 36/39] feat(liquidationVisibility): handle errors that might arise from other vats --- .../src/vaultFactory/vaultManager.js | 92 ++++++++++++++----- 1 file changed, 69 insertions(+), 23 deletions(-) diff --git a/packages/inter-protocol/src/vaultFactory/vaultManager.js b/packages/inter-protocol/src/vaultFactory/vaultManager.js index d36b4e65327..e0541cc45b8 100644 --- a/packages/inter-protocol/src/vaultFactory/vaultManager.js +++ b/packages/inter-protocol/src/vaultFactory/vaultManager.js @@ -51,7 +51,7 @@ import { TopicsRecordShape, } from '@agoric/zoe/src/contractSupport/index.js'; import { PriceQuoteShape, SeatShape } from '@agoric/zoe/src/typeGuards.js'; -import { E } from '@endo/eventual-send'; +import { E, Far } from '@endo/far'; import { TimestampShape } from '@agoric/time'; import { AuctionPFShape } from '../auction/auctioneer.js'; import { @@ -746,20 +746,46 @@ export const prepareVaultManagerKit = ( collateralSold: plan.collateralSold, collateralRemaining: plan.collatRemaining, // @ts-expect-error - endTime: auctionSchedule.liveAuctionSchedule.endTime, // write if nor rejected + // eslint-disable-next-line @endo/no-optional-chaining + endTime: auctionSchedule?.liveAuctionSchedule.endTime, }; return E( liquidationRecorderKits.auctionResultRecorderKit.recorder, ).writeFinal(auctionResultState); }; - return harden({ + return Far('Liquidation Visibility Writers', { writePreAuction, writePostAuction, writeAuctionResults, }); }, + /** + * This method checks if liquidationVisibilityWriters is undefined or + * not in case of a rejected promise when creating the writers. If + * liquidationVisibilityWriters is undefined it silently notifies the + * console. Otherwise, it goes on with the writing. + * + * @param {LiquidationVisibilityWriters} liquidationVisibilityWriters + * @param {[string, object][]} writes + */ + async writeLiqVisibility(liquidationVisibilityWriters, writes) { + console.log('WRITES', writes); + if (!liquidationVisibilityWriters) { + trace( + 'writeLiqVisibility', + `Error: liquidationVisibilityWriters is ${liquidationVisibilityWriters}`, + ); + return; + } + + for (const [methodName, params] of writes) { + trace('DEBUG', methodName, params); + void liquidationVisibilityWriters[methodName](params); + } + }, + /** * @param {TimestampRecord} timestamp * @returns {Promise} @@ -1354,22 +1380,31 @@ export const prepareVaultManagerKit = ( ), ); - const [{ userSeatPromise, deposited }, liquidationVisibilityWriters] = - await Promise.all([ + // TODO: Explain what happens in here + await null; + const [ + { userSeatPromise, deposited }, + liquidationVisibilityWriters, + auctionSchedule, + ] = ( + await Promise.allSettled([ makeDeposit, helper.makeLiquidationVisibilityWriters(timestamp), - ]); - - void liquidationVisibilityWriters.writePreAuction(vaultData); + schedulesP, + ]) + ) + .filter(result => result.status === 'fulfilled') + // @ts-expect-error + .map(result => result.value); + + void helper.writeLiqVisibility(liquidationVisibilityWriters, [ + ['writePreAuction', vaultData], + ]); // This is expected to wait for the duration of the auction, which // is controlled by the auction parameters startFrequency, clockStep, // and the difference between startingRate and lowestRate. - const [auctionSchedule, proceeds] = await Promise.all([ - schedulesP, - deposited, - userSeatPromise, - ]); + const [proceeds] = await Promise.all([deposited, userSeatPromise]); const { storedCollateralQuote } = collateralEphemera( this.state.collateralBrand, @@ -1397,16 +1432,27 @@ export const prepareVaultManagerKit = ( vaultsInPlan, }); - void liquidationVisibilityWriters.writeAuctionResults({ - plan, - totalCollateral, - totalDebt, - auctionSchedule, - }); - void liquidationVisibilityWriters.writePostAuction({ - plan, - vaultsInPlan, - }); + void helper.writeLiqVisibility( + liquidationVisibilityWriters, + harden([ + [ + 'writeAuctionResults', + { + plan, + totalCollateral, + totalDebt, + auctionSchedule, + }, + ], + [ + 'writePostAuction', + { + plan, + vaultsInPlan, + }, + ], + ]), + ); } catch (err) { console.error('🚨 Error distributing proceeds:', err); } From 6920d1a522e49faeb56b11d0dcf8bc9d2a934360 Mon Sep 17 00:00:00 2001 From: anilhelvaci Date: Wed, 31 Jan 2024 14:22:28 +0300 Subject: [PATCH 37/39] fix(liquidationVisibility): explain Promise.allSettled --- packages/inter-protocol/src/vaultFactory/vaultManager.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/inter-protocol/src/vaultFactory/vaultManager.js b/packages/inter-protocol/src/vaultFactory/vaultManager.js index e0541cc45b8..2ce7b9b90b0 100644 --- a/packages/inter-protocol/src/vaultFactory/vaultManager.js +++ b/packages/inter-protocol/src/vaultFactory/vaultManager.js @@ -1380,7 +1380,11 @@ export const prepareVaultManagerKit = ( ), ); - // TODO: Explain what happens in here + // helper.makeLiquidationVisibilityWriters and schedulesP depends on others vats, + // so we switched from Promise.all to Promise.allSettled because if one of those vats fail + // we don't want those failures to prevent liquidation process from going forward. + // We don't handle the case where 'makeDeposit' rejects as liquidation depends on + // 'makeDeposit' being fulfilled. await null; const [ { userSeatPromise, deposited }, From dd3fbdbbf0331fcb978f9c457006a3120fadd9aa Mon Sep 17 00:00:00 2001 From: anilhelvaci Date: Wed, 31 Jan 2024 14:25:22 +0300 Subject: [PATCH 38/39] fix(liquidationVisibility): lint fix --- .../test-liquidationVisibility.js | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js b/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js index 4963cfb2c31..e660c969e00 100644 --- a/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js +++ b/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js @@ -53,7 +53,6 @@ import { assertVaultNotification, } from './assertions.js'; import { Phase } from '../vaultFactory/driver.js'; -import { TimeMath } from '@agoric/time'; const trace = makeTracer('TestLiquidationVisibility', false); @@ -183,13 +182,13 @@ test('liq-result-scenario-1', async t => { path: `vaultFactory.managers.manager0.liquidations.${time.absValue.toString()}.vaults.preAuction`, expected: [ [ - "vault0", + 'vault0', { - collateralAmount: collateralAmount, - debtAmount: debtDuringLiquidation + collateralAmount, + debtAmount: debtDuringLiquidation, }, - ] - ] + ], + ], }); await assertVaultState(t, vaultNotifier, 'liquidating'); @@ -227,13 +226,13 @@ test('liq-result-scenario-1', async t => { path: `vaultFactory.managers.manager0.liquidations.${time.absValue.toString()}.vaults.preAuction`, expected: [ [ - "vault0", + 'vault0', { - collateralAmount: collateralAmount, - debtAmount: debtDuringLiquidation + collateralAmount, + debtAmount: debtDuringLiquidation, }, - ] - ] + ], + ], }); // TODO: postAuction is not filled yet @@ -254,7 +253,6 @@ test('liq-result-scenario-1', async t => { // ] // }); - // Check that {timestamp}.auctionResult values are correct after auction is completed await assertStorageData({ t, @@ -268,11 +266,11 @@ test('liq-result-scenario-1', async t => { mintedProceeds: run.make(1680n), collateralSold: aeth.make(400n), collateralRemaining: aeth.makeEmpty(), - endTime: endTime, + endTime, }, }); - // Create snapshot of the storage node + // Create snapshot of the storage node await documentStorageSchema(t, chainStorage, { note: 'Scenario 1 Liquidation Visibility Snapshot', node: `vaultFactory.managers.manager0.liquidations.${time.absValue.toString()}`, From 728d69557b63ea81adbfa97d721d2dffe0ccbfdb Mon Sep 17 00:00:00 2001 From: anilhelvaci Date: Wed, 31 Jan 2024 14:28:14 +0300 Subject: [PATCH 39/39] chore(liquidationVisibility): uncomment post auction assertion in `liq-result-scenario-1` --- .../test-liquidationVisibility.js | 23 +++++-------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js b/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js index e660c969e00..0dd53228a53 100644 --- a/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js +++ b/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js @@ -235,23 +235,12 @@ test('liq-result-scenario-1', async t => { ], }); - // TODO: postAuction is not filled yet - // should be empty - // await assertStorageData({ - // t, - // storageRoot: chainStorage, - // path: `vaultFactory.managers.manager0.liquidations.${time.absValue.toString()}.vaults.postAuction`, - // expected: [ - // [ - // "vault0", - // { - // collateralAmount: collateralAmount, - // debtAmount: debtDuringLiquidation, - // phase: Phase.LIQUIDATED, - // }, - // ] - // ] - // }); + await assertStorageData({ + t, + storageRoot: chainStorage, + path: `vaultFactory.managers.manager0.liquidations.${time.absValue.toString()}.vaults.postAuction`, + expected: [], + }); // Check that {timestamp}.auctionResult values are correct after auction is completed await assertStorageData({