From a26eec21dc29e3990d080ce7649c26f367471489 Mon Sep 17 00:00:00 2001 From: JorgeLopes-BytePitch Date: Wed, 31 Jan 2024 16:18:32 +0000 Subject: [PATCH] Squashed commit of the following: commit 728d69557b63ea81adbfa97d721d2dffe0ccbfdb Author: anilhelvaci Date: Wed Jan 31 14:28:14 2024 +0300 chore(liquidationVisibility): uncomment post auction assertion in `liq-result-scenario-1` commit dd3fbdbbf0331fcb978f9c457006a3120fadd9aa Author: anilhelvaci Date: Wed Jan 31 14:25:22 2024 +0300 fix(liquidationVisibility): lint fix commit 6920d1a522e49faeb56b11d0dcf8bc9d2a934360 Author: anilhelvaci Date: Wed Jan 31 14:22:28 2024 +0300 fix(liquidationVisibility): explain Promise.allSettled commit 732e1d7e7794937c2a9d4b5aff1f2d61b3d0ba92 Author: anilhelvaci Date: Wed Jan 31 11:37:45 2024 +0300 feat(liquidationVisibility): handle errors that might arise from other vats commit 683f56d0bd474483e74b4ac8709493c90c07d274 Author: anilhelvaci Date: Tue Jan 30 14:31:13 2024 +0300 feat(liquidationVisibility): add LiquidationVisibilityWriters to improve readability, fetch schedule during the auction itself commit 4c45f2a7730ebb05a2a4fa355debf82cb00c57a6 Author: anilhelvaci Date: Tue Jan 30 10:30:02 2024 +0300 fix(liquidationVisibility): add pattern matcher to `getVaultState` --- .../src/vaultFactory/liquidation.js | 14 +- .../inter-protocol/src/vaultFactory/types.js | 25 +++ .../inter-protocol/src/vaultFactory/vault.js | 4 +- .../src/vaultFactory/vaultManager.js | 201 +++++++++++++----- .../test-liquidationVisibility.js | 51 ++--- 5 files changed, 206 insertions(+), 89 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/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), diff --git a/packages/inter-protocol/src/vaultFactory/vaultManager.js b/packages/inter-protocol/src/vaultFactory/vaultManager.js index 40e049b76e7..2ce7b9b90b0 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 { @@ -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,107 @@ 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 + // eslint-disable-next-line @endo/no-optional-chaining + endTime: auctionSchedule?.liveAuctionSchedule.endTime, + }; + return E( + liquidationRecorderKits.auctionResultRecorderKit.recorder, + ).writeFinal(auctionResultState); + }; + + 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} */ async makeLiquidationRecorderKits(timestamp) { @@ -1193,7 +1296,7 @@ export const prepareVaultManagerKit = ( }, /** * @param {ERef} auctionPF - * @param {{ absValue: bigint }} timestamp + * @param {TimestampRecord} timestamp */ async liquidateVaults(auctionPF, timestamp) { const { state, facets } = this; @@ -1258,6 +1361,7 @@ export const prepareVaultManagerKit = ( liquidatingVaults.getSize(), totalCollateral, ); + const schedulesP = E(auctionPF).getSchedules(); helper.markLiquidating(totalDebt, totalCollateral); void helper.writeMetrics(); @@ -1276,31 +1380,35 @@ export const prepareVaultManagerKit = ( ), ); - const [{ userSeatPromise, deposited }, liquidationRecorderKits] = - await Promise.all([ + // 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 }, + liquidationVisibilityWriters, + auctionSchedule, + ] = ( + await Promise.allSettled([ makeDeposit, - helper.makeLiquidationRecorderKits(timestamp), - ]); - - /** @type PreAuctionState */ - const preAuctionState = [...vaultData.entries()].map( - ([vault, data]) => [ - `vault${vault.getVaultState().idInManager}`, - { ...data }, - ], - ); + helper.makeLiquidationVisibilityWriters(timestamp), + 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([ - E(auctionPF).getSchedules(), - deposited, - userSeatPromise, - E( - liquidationRecorderKits.preAuctionRecorderKit.recorder, - ).writeFinal(preAuctionState), - ]); + const [proceeds] = await Promise.all([deposited, userSeatPromise]); const { storedCollateralQuote } = collateralEphemera( this.state.collateralBrand, @@ -1328,34 +1436,27 @@ 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 helper.writeLiqVisibility( + liquidationVisibilityWriters, + harden([ + [ + 'writeAuctionResults', + { + plan, + totalCollateral, + totalDebt, + auctionSchedule, + }, + ], + [ + 'writePostAuction', + { + plan, + vaultsInPlan, + }, + ], + ]), ); - void E( - liquidationRecorderKits.postAuctionRecorderKit.recorder, - ).writeFinal(postAuctionState); } catch (err) { console.error('🚨 Error distributing proceeds:', err); } diff --git a/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js b/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js index 4963cfb2c31..0dd53228a53 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,33 +226,21 @@ 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 - // 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({ @@ -268,11 +255,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()}`,