diff --git a/packages/inter-protocol/src/vaultFactory/types.js b/packages/inter-protocol/src/vaultFactory/types.js index c78f9cc80c0..8976e630ce4 100644 --- a/packages/inter-protocol/src/vaultFactory/types.js +++ b/packages/inter-protocol/src/vaultFactory/types.js @@ -16,6 +16,9 @@ * @typedef {import('@agoric/time/src/types.js').TimestampRecord} TimestampRecord * * @typedef {import('@agoric/time').RelativeTime} RelativeTime + * + * @typedef {import('./liquidation.js').VaultData} VaultData + * @typedef {import('./proceeds.js').DistributionPlan} DistributionPlan */ /** @@ -138,12 +141,12 @@ /** * @typedef {{ - * plan: import('./proceeds.js').DistributionPlan; - * vaultsInPlan: Array; + * plan: DistributionPlan | undefined; + * vaultsInPlan: Array | undefined; * }} PostAuctionParams * * @typedef {{ - * plan: import('./proceeds.js').DistributionPlan; + * plan: DistributionPlan | undefined; * totalCollateral: Amount<'nat'>; * totalDebt: Amount<'nat'>; * auctionSchedule: import('../auction/scheduler.js').FullSchedule; @@ -151,7 +154,6 @@ */ /** - * @typedef {import('./liquidation.js').VaultData} VaultData * * @typedef {object} LiquidationVisibilityWriters * @property {(vaultData: VaultData) => Promise} writePreAuction diff --git a/packages/inter-protocol/src/vaultFactory/vaultManager.js b/packages/inter-protocol/src/vaultFactory/vaultManager.js index 0256b4895f5..afdc99925ef 100644 --- a/packages/inter-protocol/src/vaultFactory/vaultManager.js +++ b/packages/inter-protocol/src/vaultFactory/vaultManager.js @@ -24,7 +24,7 @@ import { NotifierShape, RatioShape, } from '@agoric/ertp'; -import { allValuesSettled, makeTracer } from '@agoric/internal'; +import { makeTracer } from '@agoric/internal'; import { makeStoredNotifier, observeNotifier } from '@agoric/notifier'; import { appendToStoredArray } from '@agoric/store/src/stores/store-utils.js'; import { @@ -165,6 +165,10 @@ const trace = makeTracer('VM'); */ /** + * @typedef {{ + * error: string + * }} DistributionError + * * @typedef {( * | string * | { collateralAmount: Amount<'nat'>; debtAmount: Amount<'nat'> } @@ -186,8 +190,8 @@ const trace = makeTracer('VM'); * * @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; + * postAuctionRecorderKit: import('@agoric/zoe/src/contractSupport/recorder.js').RecorderKit; + * auctionResultRecorderKit: import('@agoric/zoe/src/contractSupport/recorder.js').RecorderKit; * }} LiquidationRecorderKits */ @@ -667,6 +671,11 @@ export const prepareVaultManagerKit = ( * @returns {Promise} */ const writePostAuction = ({ plan, vaultsInPlan }) => { + if (!(plan && vaultsInPlan)) { + return E( + liquidationRecorderKits.postAuctionRecorderKit.recorder, + ).writeFinal({ error: 'Error distributing proceeds' }); + } /** @type PostAuctionState */ const postAuctionState = plan.transfersToVault.map( ([id, transfer]) => [ @@ -689,6 +698,12 @@ export const prepareVaultManagerKit = ( totalDebt, auctionSchedule, }) => { + if (!plan) { + return E( + liquidationRecorderKits.auctionResultRecorderKit.recorder, + ).writeFinal({ error: 'Error distributing proceeds' }); + } + /** @type AuctionResultState */ const auctionResultState = { collateralOffered: totalCollateral, @@ -1296,24 +1311,18 @@ export const prepareVaultManagerKit = ( ), ); - // 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. - const { - makeDeposit: { userSeatPromise, deposited }, + const [ + { userSeatPromise, deposited }, liquidationVisibilityWriters, auctionSchedule, - } = await allValuesSettled({ + ] = await Promise.all([ makeDeposit, - liquidationVisibilityWriters: - helper.makeLiquidationVisibilityWriters(timestamp), - auctionSchedule: schedulesP, - }); + helper.makeLiquidationVisibilityWriters(timestamp), + schedulesP, + ]); if (helper.checkWritersPresent(liquidationVisibilityWriters)) { - liquidationVisibilityWriters.writePreAuction(vaultData); + void liquidationVisibilityWriters.writePreAuction(vaultData); } // This is expected to wait for the duration of the auction, which @@ -1357,11 +1366,11 @@ export const prepareVaultManagerKit = ( } if (helper.checkWritersPresent(liquidationVisibilityWriters)) { - liquidationVisibilityWriters.writePostAuction({ + void liquidationVisibilityWriters.writePostAuction({ plan, vaultsInPlan, }); - liquidationVisibilityWriters.writeAuctionResults({ + void liquidationVisibilityWriters.writeAuctionResults({ plan, totalCollateral, totalDebt, diff --git a/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js b/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js index 3e939e14c59..b399abb31fd 100644 --- a/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js +++ b/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js @@ -1201,289 +1201,3 @@ test('liq-no-vaults', async t => { ); t.is(vstorageDuringLiquidation.length, 0); }); - -/* The auctionSchedule returned schedulesP will be a rejected promise - * In this scenario, the state of auctionResult node should have endTime as undefined */ -test('liq-rejected-schedule', 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: { vaultFactory, aethCollateralManager }, - aethTestPriceAuthority, - reserveKit: { reserveCreatorFacet, reservePublicFacet }, - auctioneerKit, - chainStorage, - } = services; - - const { reserveTracker } = await getMetricTrackers({ - t, - collateralManager: aethCollateralManager, - reservePublicFacet, - }); - - let expectedReserveState = reserveInitialState(run.makeEmpty()); - 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 openVault({ - t, - cm: aethCollateralManager, - collateralAmount, - colKeyword: 'aeth', - wantMintedAmount: wantMinted, - }); - - // 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, aeth); - - // 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 eventLoopIteration(); - await assertVaultState(t, vaultNotifier, 'active'); - - await E(auctioneerKit.publicFacet).setRejectGetSchedules(true); - - const { startTime, time } = 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, - debtAmount: debtDuringLiquidation, - }, - ], - ], - }); - - await assertVaultState(t, vaultNotifier, 'liquidating'); - await assertVaultCollateral(t, vault, 0n, aeth); - await assertVaultCurrentDebt(t, vault, wantMinted); - - await E(auctioneerKit.publicFacet).setRejectGetSchedules(false); - - 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, aeth); - await assertVaultCurrentDebt(t, vault, 0n); - await assertVaultFactoryRewardAllocation(t, vaultFactory, 80n); - - const closeSeat = await closeVault({ t, vault }); - await E(closeSeat).getOfferResult(); - - await assertCollateralProceeds(t, closeSeat, aeth.makeEmpty(), aeth.issuer); - await assertVaultCollateral(t, vault, 0n, aeth); - await assertBidderPayout(t, bidderSeat, run, 320n, aeth, 400n); - - expectedReserveState = { - allocations: { - Aeth: undefined, - Fee: undefined, - }, - }; - 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, - debtAmount: debtDuringLiquidation, - }, - ], - ], - }); - - 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({ - 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: undefined, - startTime: undefined, - }, - }); -}); - -/* The timestampStorageNode returned makeChildNode will be a rejected promise - * In this scenario, the error should be handled and printed its message */ -test('liq-rejected-timestampStorageNode', 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: { vaultFactory, aethCollateralManager }, - aethTestPriceAuthority, - reserveKit: { reserveCreatorFacet, reservePublicFacet }, - auctioneerKit, - chainStorage, - childrenNodes, - } = services; - - const { reserveTracker } = await getMetricTrackers({ - t, - collateralManager: aethCollateralManager, - reservePublicFacet, - }); - - await E(reserveCreatorFacet).addIssuer(aeth.issuer, 'Aeth'); - - const collateralAmount = aeth.make(400n); - const wantMinted = run.make(1600n); - - const vaultSeat = await openVault({ - t, - cm: aethCollateralManager, - collateralAmount, - colKeyword: 'aeth', - wantMintedAmount: wantMinted, - }); - - // 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); - - // 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); - - const liquiationNode = await childrenNodes.get('liquidations'); - liquiationNode.toggleChildrenBlocked(); - - // drop collateral price from 5:1 to 4:1 and liquidate vault - aethTestPriceAuthority.setPrice(makeRatio(40n, run.brand, 10n, aeth.brand)); - await eventLoopIteration(); - - await assertVaultState(t, vaultNotifier, 'active'); - - const { startTime } = await startAuctionClock(auctioneerKit, manualTimer); - - // Check that no child node with auction start time's name created after the liquidation - const vstorageDuringLiquidation = await getDataFromVstorage( - chainStorage, - `vaultFactory.managers.manager0.liquidations`, - ); - t.is(vstorageDuringLiquidation.length, 0); - - await assertVaultState(t, vaultNotifier, 'liquidating'); - await assertVaultCollateral(t, vault, 0n, aeth); - await assertVaultCurrentDebt(t, vault, wantMinted); - - const 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, aeth); - await assertVaultCurrentDebt(t, vault, 0n); - await assertVaultFactoryRewardAllocation(t, vaultFactory, 80n); - - const closeSeat = await closeVault({ t, vault }); - await E(closeSeat).getOfferResult(); - - await assertCollateralProceeds(t, closeSeat, aeth.makeEmpty(), aeth.issuer); - await assertVaultCollateral(t, vault, 0n, aeth); - await assertBidderPayout(t, bidderSeat, run, 320n, aeth, 400n); - - await assertReserveState(reserveTracker, 'like', { - allocations: { - Aeth: undefined, - Fee: undefined, - }, - }); -});