From 0d0d493be45dcd9978ee5d28b3fb65ba59b58f0e Mon Sep 17 00:00:00 2001 From: JorgeLopes-BytePitch Date: Wed, 31 Jan 2024 16:18:32 +0000 Subject: [PATCH 01/20] 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()}`, From 9d45370e0ea58050f54f656438e9eb3c808a88fc Mon Sep 17 00:00:00 2001 From: JorgeLopes-BytePitch Date: Thu, 1 Feb 2024 12:21:35 +0000 Subject: [PATCH 02/20] chore(liquidationVisibility): #4 add auctioneer wrapper and update setupBasics --- .../auctioneer-contract-wrapper.js | 752 ++++++++++++++++++ .../test/liquidationVisibility/tools.js | 16 +- 2 files changed, 762 insertions(+), 6 deletions(-) create mode 100644 packages/inter-protocol/test/liquidationVisibility/auctioneer-contract-wrapper.js diff --git a/packages/inter-protocol/test/liquidationVisibility/auctioneer-contract-wrapper.js b/packages/inter-protocol/test/liquidationVisibility/auctioneer-contract-wrapper.js new file mode 100644 index 00000000000..d089e6909b3 --- /dev/null +++ b/packages/inter-protocol/test/liquidationVisibility/auctioneer-contract-wrapper.js @@ -0,0 +1,752 @@ +import '@agoric/governance/exported.js'; +import '@agoric/zoe/exported.js'; +import '@agoric/zoe/src/contracts/exported.js'; + +import { AmountMath, AmountShape, BrandShape } from '@agoric/ertp'; +import { handleParamGovernance } from '@agoric/governance'; +import { BASIS_POINTS, makeTracer } from '@agoric/internal'; +import { prepareDurablePublishKit } from '@agoric/notifier'; +import { mustMatch } from '@agoric/store'; +import { appendToStoredArray } from '@agoric/store/src/stores/store-utils.js'; +import { M, provideDurableMapStore } from '@agoric/vat-data'; +import { + atomicRearrange, + ceilDivideBy, + ceilMultiplyBy, + defineERecorderKit, + defineRecorderKit, + floorDivideBy, + floorMultiplyBy, + makeRatio, + makeRatioFromAmounts, + makeRecorderTopic, + natSafeMath, + prepareRecorder, + provideEmptySeat, + offerTo, +} from '@agoric/zoe/src/contractSupport/index.js'; +import { FullProposalShape } from '@agoric/zoe/src/typeGuards.js'; +import { E } from '@endo/eventual-send'; +import { Far } from '@endo/marshal'; + +import { makeNatAmountShape } from '../../src/contractSupport.js'; +import { + makeOfferSpecShape, + prepareAuctionBook, +} from '../../src/auction/auctionBook.js'; +import { auctioneerParamTypes } from '../../src/auction/params.js'; +import { makeScheduler } from '../../src/auction/scheduler.js'; +import { AuctionState } from '../../src/auction/util.js'; + +/** @typedef {import('@agoric/vat-data').Baggage} Baggage */ + +const { Fail, quote: q } = assert; +const { add, multiply } = natSafeMath; + +const trace = makeTracer('Auction', true); + +/** + * @file In this file, 'Bid' is the name of the ERTP issuer used to purchase + * collateral from various issuers. It's too confusing to also use Bid as a + * verb or a description of amounts offered, so we've tried to find + * alternatives in all those cases. + */ + +const MINIMUM_BID_GIVE = 1n; + +/** + * @param {NatValue} rate + * @param {Brand<'nat'>} bidBrand + * @param {Brand<'nat'>} collateralBrand + */ +const makeBPRatio = (rate, bidBrand, collateralBrand = bidBrand) => + makeRatioFromAmounts( + AmountMath.make(bidBrand, rate), + AmountMath.make(collateralBrand, BASIS_POINTS), + ); + +/** + * The auction sold some amount of collateral, and raised a certain amount of + * Bid. The excess collateral was returned as `unsoldCollateral`. The Bid amount + * collected from the auction participants is `proceeds`. + * + * Return a set of transfers for atomicRearrange() that distribute + * `unsoldCollateral` and `proceeds` proportionally to each seat's deposited + * amount. Any uneven split should be allocated to the reserve. + * + * @param {Amount} unsoldCollateral + * @param {Amount} proceeds + * @param {{ seat: ZCFSeat; amount: Amount<'nat'>; goal: Amount<'nat'> }[]} deposits + * @param {ZCFSeat} collateralSeat + * @param {ZCFSeat} bidHoldingSeat seat with the Bid allocation to be + * distributed + * @param {string} collateralKeyword The Reserve will hold multiple collaterals, + * so they need distinct keywords + * @param {ZCFSeat} reserveSeat + * @param {Brand} brand + */ +const distributeProportionalShares = ( + unsoldCollateral, + proceeds, + deposits, + collateralSeat, + bidHoldingSeat, + collateralKeyword, + reserveSeat, + brand, +) => { + const totalCollDeposited = deposits.reduce((prev, { amount }) => { + return AmountMath.add(prev, amount); + }, AmountMath.makeEmpty(brand)); + + const collShare = makeRatioFromAmounts(unsoldCollateral, totalCollDeposited); + const currShare = makeRatioFromAmounts(proceeds, totalCollDeposited); + /** @type {TransferPart[]} */ + const transfers = []; + let proceedsLeft = proceeds; + let collateralLeft = unsoldCollateral; + + // each depositor gets a share that equals their amount deposited + // divided by the total deposited multiplied by the Bid and + // collateral being distributed. + for (const { seat, amount } of deposits.values()) { + const currPortion = floorMultiplyBy(amount, currShare); + proceedsLeft = AmountMath.subtract(proceedsLeft, currPortion); + const collPortion = floorMultiplyBy(amount, collShare); + collateralLeft = AmountMath.subtract(collateralLeft, collPortion); + transfers.push([bidHoldingSeat, seat, { Bid: currPortion }]); + transfers.push([collateralSeat, seat, { Collateral: collPortion }]); + } + + transfers.push([bidHoldingSeat, reserveSeat, { Bid: proceedsLeft }]); + + if (!AmountMath.isEmpty(collateralLeft)) { + transfers.push([ + collateralSeat, + reserveSeat, + { Collateral: collateralLeft }, + { [collateralKeyword]: collateralLeft }, + ]); + } + + return transfers; +}; + +/** + * The auction sold some amount of collateral, and raised a certain amount of + * Bid. The excess collateral was returned as `unsoldCollateral`. The Bid amount + * collected from the auction participants is `proceeds`. + * + * Return a set of transfers for atomicRearrange() that distribute + * `unsoldCollateral` and `proceeds` proportionally to each seat's deposited + * amount. Any uneven split should be allocated to the reserve. + * + * This function is exported for testability, and is not expected to be used + * outside the contract below. + * + * Some or all of the depositors may have specified a goal amount. + * + * - A if none did, return collateral and Bid prorated to deposits. + * - B if proceeds < proceedsGoal everyone gets prorated amounts of both. + * - C if proceeds matches proceedsGoal, everyone gets the Bid they asked for, + * plus enough collateral to reach the same proportional payout. If any + * depositor's goal amount exceeded their share of the total, we'll fall back + * to the first approach. + * - D if proceeds > proceedsGoal && all depositors specified a limit, all + * depositors get their goal first, then we distribute the remainder + * (collateral and Bid) to get the same proportional payout. + * - E if proceeds > proceedsGoal && some depositors didn't specify a limit, + * depositors who did will get their goal first, then we distribute the + * remainder (collateral and Bid) to get the same proportional payout. If any + * depositor's goal amount exceeded their share of the total, we'll fall back + * as above. Think of it this way: those who specified a limit want as much + * collateral back as possible, consistent with raising a certain amount of + * Bid. Those who didn't specify a limit are trying to sell collateral, and + * would prefer to have as much as possible converted to Bid. + * + * @param {Amount<'nat'>} unsoldCollateral + * @param {Amount<'nat'>} proceeds + * @param {{ seat: ZCFSeat; amount: Amount<'nat'>; goal: Amount<'nat'> }[]} deposits + * @param {ZCFSeat} collateralSeat + * @param {ZCFSeat} bidHoldingSeat seat with the Bid allocation to be + * distributed + * @param {string} collateralKeyword The Reserve will hold multiple collaterals, + * so they need distinct keywords + * @param {ZCFSeat} reserveSeat + * @param {Brand} brand + */ +export const distributeProportionalSharesWithLimits = ( + unsoldCollateral, + proceeds, + deposits, + collateralSeat, + bidHoldingSeat, + collateralKeyword, + reserveSeat, + brand, +) => { + trace('distributeProportionally with limits'); + // unmatched is the sum of the deposits by those who didn't specify a goal + const [collDeposited, proceedsGoal, unmatchedDeposits] = deposits.reduce( + (prev, { amount, goal }) => { + const nextDeposit = AmountMath.add(prev[0], amount); + const [proceedsSum, unmatchedSum] = goal + ? [AmountMath.add(goal, prev[1]), prev[2]] + : [prev[1], AmountMath.add(prev[2], amount)]; + return [nextDeposit, proceedsSum, unmatchedSum]; + }, + [ + AmountMath.makeEmpty(brand), + AmountMath.makeEmptyFromAmount(proceeds), + AmountMath.makeEmpty(brand), + ], + ); + + const distributeProportionally = () => + distributeProportionalShares( + unsoldCollateral, + proceeds, + deposits, + collateralSeat, + bidHoldingSeat, + collateralKeyword, + reserveSeat, + brand, + ); + + // cases A and B + if ( + AmountMath.isEmpty(proceedsGoal) || + !AmountMath.isGTE(proceeds, proceedsGoal) + ) { + return distributeProportionally(); + } + + // Calculate multiplier for collateral that gives total value each depositor + // should get. + // + // The average price of collateral is proceeds / CollateralSold. + // The value of Collateral is Price * unsoldCollateral. + // The overall total value to be distributed is + // Proceeds + collateralValue. + // Each depositor should get bid and collateral that sum to the overall + // total value multiplied by the ratio of that depositor's collateral + // deposited to all the collateral deposited. + // + // To improve the resolution of the result, we only divide once, so we + // multiply each depositor's collateral remaining by this expression. + // + // collSold * proceeds + proceeds * unsoldCollateral + // ----------------------------------------------------------- + // collSold * totalCollDeposit + // + // If you do the dimension analysis, we'll multiply collateral by a ratio + // representing Bid/collateral. + + // average value of collateral is collateralSold / proceeds + const collateralSold = AmountMath.subtract(collDeposited, unsoldCollateral); + const numeratorValue = add( + multiply(collateralSold.value, proceeds.value), + multiply(unsoldCollateral.value, proceeds.value), + ); + const denominatorValue = multiply(collateralSold.value, collDeposited.value); + const totalValueRatio = makeRatioFromAmounts( + AmountMath.make(proceeds.brand, numeratorValue), + AmountMath.make(brand, denominatorValue), + ); + + const avgPrice = makeRatioFromAmounts(proceeds, collateralSold); + + // Allocate the proceedsGoal amount to depositors who specified it. Add + // collateral to reach their share. Then see what's left, and allocate it + // among the remaining depositors. Escape to distributeProportionalShares if + // anything doesn't work. + /** @type {TransferPart[]} */ + const transfers = []; + let proceedsLeft = proceeds; + let collateralLeft = unsoldCollateral; + + // case C + if (AmountMath.isEqual(proceedsGoal, proceeds)) { + // each depositor gets a share that equals their amount deposited + // multiplied by totalValueRatio computed above. + + for (const { seat, amount, goal } of deposits.values()) { + const depositorValue = floorMultiplyBy(amount, totalValueRatio); + if (goal === null || AmountMath.isGTE(depositorValue, goal)) { + let valueNeeded = depositorValue; + if (goal !== null && !AmountMath.isEmpty(goal)) { + proceedsLeft = AmountMath.subtract(proceedsLeft, goal); + transfers.push([bidHoldingSeat, seat, { Bid: goal }]); + valueNeeded = AmountMath.subtract(depositorValue, goal); + } + + const collateralToAdd = floorDivideBy(valueNeeded, avgPrice); + collateralLeft = AmountMath.subtract(collateralLeft, collateralToAdd); + transfers.push([collateralSeat, seat, { Collateral: collateralToAdd }]); + } else { + // This depositor asked for more than their share. + // ignore `transfers` and distribute everything proportionally. + return distributeProportionally(); + } + } + } else { + // Cases D & E. Proceeds > proceedsGoal, so those who specified a limit + // receive at least their target. + + const collateralValue = floorMultiplyBy(unsoldCollateral, avgPrice); + const totalDistributableValue = AmountMath.add(proceeds, collateralValue); + // The share for those who specified a limit is proportional to their + // collateral. ceiling because it's a lower limit on the restrictive branch + const limitedShare = ceilMultiplyBy( + AmountMath.subtract(collDeposited, unmatchedDeposits), + makeRatioFromAmounts(totalDistributableValue, collDeposited), + ); + + // if proceedsGoal + value of unsoldCollateral >= limitedShare then those + // who specified a limit can get all the excess over their limit in + // collateral. Others share whatever is left. + // If proceedsGoal + unsoldCollateral < limitedShare then those who + // specified share all the collateral, and everyone gets Bid to cover + // the remainder of their share. + const limitedGetMaxCollateral = AmountMath.isGTE( + AmountMath.add(proceedsGoal, collateralValue), + limitedShare, + ); + + const calcNotLimitedCollateralShare = () => { + if (limitedGetMaxCollateral) { + // those who limited will get limitedShare - proceedsGoal in collateral + const ltdCollatValue = AmountMath.subtract(limitedShare, proceedsGoal); + const ltdCollatShare = ceilDivideBy(ltdCollatValue, avgPrice); + // the unlimited will get the remainder of the collateral + return AmountMath.subtract(unsoldCollateral, ltdCollatShare); + } else { + return AmountMath.makeEmpty(brand); + } + }; + const notLimitedCollateralShare = calcNotLimitedCollateralShare(); + + for (const { seat, amount, goal } of deposits.values()) { + const depositorValue = floorMultiplyBy(amount, totalValueRatio); + + const addRemainderInBid = collateralAdded => { + const collateralVal = ceilMultiplyBy(collateralAdded, avgPrice); + /** @type {Amount<'nat'>} XXX for package depth type resolution */ + const valueNeeded = AmountMath.subtract(depositorValue, collateralVal); + + proceedsLeft = AmountMath.subtract(proceedsLeft, valueNeeded); + transfers.push([bidHoldingSeat, seat, { Bid: valueNeeded }]); + }; + + if (goal === null || AmountMath.isEmpty(goal)) { + const collateralShare = floorMultiplyBy( + notLimitedCollateralShare, + makeRatioFromAmounts(amount, unmatchedDeposits), + ); + collateralLeft = AmountMath.subtract(collateralLeft, collateralShare); + addRemainderInBid(collateralShare); + transfers.push([collateralSeat, seat, { Collateral: collateralShare }]); + } else if (limitedGetMaxCollateral) { + proceedsLeft = AmountMath.subtract(proceedsLeft, goal); + transfers.push([bidHoldingSeat, seat, { Bid: goal }]); + + const valueNeeded = AmountMath.subtract(depositorValue, goal); + const collateralToAdd = floorDivideBy(valueNeeded, avgPrice); + collateralLeft = AmountMath.subtract(collateralLeft, collateralToAdd); + transfers.push([collateralSeat, seat, { Collateral: collateralToAdd }]); + } else { + // There's not enough collateral to completely cover the gap above + // the proceedsGoal amount, so each depositor gets a proportional share + // of unsoldCollateral plus enough Bid to reach their share. + const collateralShare = floorMultiplyBy( + unsoldCollateral, + makeRatioFromAmounts(amount, collDeposited), + ); + collateralLeft = AmountMath.subtract(collateralLeft, collateralShare); + addRemainderInBid(collateralShare); + transfers.push([collateralSeat, seat, { Collateral: collateralShare }]); + } + } + } + + transfers.push([bidHoldingSeat, reserveSeat, { Bid: proceedsLeft }]); + + if (!AmountMath.isEmpty(collateralLeft)) { + transfers.push([ + collateralSeat, + reserveSeat, + { Collateral: collateralLeft }, + { [collateralKeyword]: collateralLeft }, + ]); + } + return transfers; +}; + +/** + * @param {ZCF< + * GovernanceTerms & { + * timerService: import('@agoric/time').TimerService; + * reservePublicFacet: AssetReservePublicFacet; + * priceAuthority: PriceAuthority; + * } + * >} zcf + * @param {{ + * initialPoserInvitation: Invitation; + * storageNode: StorageNode; + * marshaller: Marshaller; + * }} privateArgs + * @param {Baggage} baggage + */ +export const start = async (zcf, privateArgs, baggage) => { + const { brands, timerService: timer, priceAuthority } = zcf.getTerms(); + timer || Fail`Timer must be in Auctioneer terms`; + const timerBrand = await E(timer).getTimerBrand(); + + const bidAmountShape = { brand: brands.Bid, value: M.nat() }; + + /** + * @type {MapStore< + * Brand, + * import('../../src/auction/auctionBook.js').AuctionBook + * >} + */ + const books = provideDurableMapStore(baggage, 'auctionBooks'); + /** + * @type {MapStore< + * Brand, + * { seat: ZCFSeat; amount: Amount<'nat'>; goal: Amount<'nat'> }[] + * >} + */ + const deposits = provideDurableMapStore(baggage, 'deposits'); + /** @type {MapStore} */ + const brandToKeyword = provideDurableMapStore(baggage, 'brandToKeyword'); + + const reserveSeat = provideEmptySeat(zcf, baggage, 'collateral'); + + let bookCounter = 0; + + const makeDurablePublishKit = prepareDurablePublishKit( + baggage, + 'Auction publish kit', + ); + const makeRecorder = prepareRecorder(baggage, privateArgs.marshaller); + + const makeRecorderKit = defineRecorderKit({ + makeRecorder, + makeDurablePublishKit, + }); + + const makeAuctionBook = prepareAuctionBook(baggage, zcf, makeRecorderKit); + + const makeERecorderKit = defineERecorderKit({ + makeRecorder, + makeDurablePublishKit, + }); + const scheduleKit = makeERecorderKit( + E(privateArgs.storageNode).makeChildNode('schedule'), + /** + * @type {import('@agoric/zoe/src/contractSupport/recorder.js').TypedMatcher< + * import('../../src/auction/scheduler.js').ScheduleNotification + * >} + */ (M.any()), + ); + + /** + * @param {ZCFSeat} seat + * @param {Amount<'nat'>} amount + * @param {Amount<'nat'> | null} goal + */ + const addDeposit = (seat, amount, goal = null) => { + appendToStoredArray(deposits, amount.brand, harden({ seat, amount, goal })); + }; + + const sendToReserve = keyword => { + const { reservePublicFacet } = zcf.getTerms(); + + const amount = reserveSeat.getCurrentAllocation()[keyword]; + if (!amount || AmountMath.isEmpty(amount)) { + return; + } + + const invitation = E(reservePublicFacet).makeAddCollateralInvitation(); + // don't wait for a response + void E.when(invitation, invite => { + const proposal = { give: { Collateral: amount } }; + void offerTo( + zcf, + invite, + { [keyword]: 'Collateral' }, + proposal, + reserveSeat, + ); + }); + }; + + // Called "discount" rate even though it can be above or below 100%. + /** @type {NatValue} */ + let currentDiscountRateBP; + + const distributeProceeds = () => { + for (const brand of deposits.keys()) { + const book = books.get(brand); + const { collateralSeat, bidHoldingSeat } = book.getSeats(); + + const depositsForBrand = deposits.get(brand); + if (depositsForBrand.length === 1) { + // send it all to the one + const liqSeat = depositsForBrand[0].seat; + + atomicRearrange( + zcf, + harden([ + [collateralSeat, liqSeat, collateralSeat.getCurrentAllocation()], + [bidHoldingSeat, liqSeat, bidHoldingSeat.getCurrentAllocation()], + ]), + ); + liqSeat.exit(); + deposits.set(brand, []); + } else if (depositsForBrand.length > 1) { + const collProceeds = collateralSeat.getCurrentAllocation().Collateral; + const currProceeds = + bidHoldingSeat.getCurrentAllocation().Bid || + AmountMath.makeEmpty(brands.Bid); + const transfers = distributeProportionalSharesWithLimits( + collProceeds, + currProceeds, + depositsForBrand, + collateralSeat, + bidHoldingSeat, + brandToKeyword.get(brand), + reserveSeat, + brand, + ); + atomicRearrange(zcf, harden(transfers)); + + for (const { seat } of depositsForBrand) { + seat.exit(); + } + + sendToReserve(brandToKeyword.get(brand)); + deposits.set(brand, []); + } + } + }; + + const { augmentPublicFacet, makeFarGovernorFacet, params } = + await handleParamGovernance( + zcf, + privateArgs.initialPoserInvitation, + auctioneerParamTypes, + privateArgs.storageNode, + privateArgs.marshaller, + ); + + const tradeEveryBook = () => { + const offerScalingRatio = makeRatio( + currentDiscountRateBP, + brands.Bid, + BASIS_POINTS, + ); + + for (const book of books.values()) { + book.settleAtNewRate(offerScalingRatio); + } + }; + + const driver = Far('Auctioneer', { + reducePriceAndTrade: () => { + trace('reducePriceAndTrade'); + + natSafeMath.isGTE(currentDiscountRateBP, params.getDiscountStep()) || + Fail`rates must fall ${currentDiscountRateBP}`; + + currentDiscountRateBP = natSafeMath.subtract( + currentDiscountRateBP, + params.getDiscountStep(), + ); + + tradeEveryBook(); + }, + finalize: () => { + trace('finalize'); + + for (const book of books.values()) { + book.endAuction(); + } + distributeProceeds(); + }, + startRound() { + trace('startRound'); + + currentDiscountRateBP = params.getStartingRate(); + for (const book of books.values()) { + book.setStartingRate(makeBPRatio(currentDiscountRateBP, brands.Bid)); + } + + tradeEveryBook(); + }, + capturePrices() { + for (const book of books.values()) { + book.captureOraclePriceForRound(); + } + }, + }); + + // eslint-disable-next-line no-use-before-define + const isActive = () => scheduler.getAuctionState() === AuctionState.ACTIVE; + + /** + * @param {ZCFSeat} zcfSeat + * @param {{ goal: Amount<'nat'> }} offerArgs + */ + const depositOfferHandler = (zcfSeat, offerArgs) => { + const goalMatcher = M.or(undefined, { goal: bidAmountShape }); + mustMatch(offerArgs, harden(goalMatcher)); + const { Collateral: collateralAmount } = zcfSeat.getCurrentAllocation(); + const book = books.get(collateralAmount.brand); + trace(`deposited ${q(collateralAmount)} goal: ${q(offerArgs?.goal)}`); + + book.addAssets(collateralAmount, zcfSeat, offerArgs?.goal); + addDeposit(zcfSeat, collateralAmount, offerArgs?.goal); + return 'deposited'; + }; + + const makeDepositInvitation = () => + zcf.makeInvitation( + depositOfferHandler, + 'deposit Collateral', + undefined, + M.splitRecord({ give: { Collateral: AmountShape } }), + ); + + const biddingProposalShape = M.splitRecord( + { + give: { + Bid: makeNatAmountShape(brands.Bid, MINIMUM_BID_GIVE), + }, + }, + { + maxBuy: M.or({ Collateral: AmountShape }, {}), + exit: FullProposalShape.exit, + }, + ); + + let rejectGetSchedules = false; + const publicFacet = augmentPublicFacet( + harden({ + /** @param {Brand<'nat'>} collateralBrand */ + makeBidInvitation(collateralBrand) { + mustMatch(collateralBrand, BrandShape); + books.has(collateralBrand) || + Fail`No book for brand ${collateralBrand}`; + const offerSpecShape = makeOfferSpecShape(brands.Bid, collateralBrand); + /** + * @param {ZCFSeat} zcfSeat + * @param {import('../../src/auction/auctionBook.js').OfferSpec} offerSpec + */ + const newBidHandler = (zcfSeat, offerSpec) => { + // xxx consider having Zoe guard the offerArgs with a provided shape + mustMatch(offerSpec, offerSpecShape); + const auctionBook = books.get(collateralBrand); + auctionBook.addOffer(offerSpec, zcfSeat, isActive()); + return 'Your bid has been accepted'; + }; + + return zcf.makeInvitation( + newBidHandler, + 'new bidding offer', + {}, + biddingProposalShape, + ); + }, + getSchedules() { + // eslint-disable-next-line no-use-before-define + if (rejectGetSchedules === true) { + return Promise.reject('getSchedules promise has failed'); + } else { + return scheduler.getSchedule(); + } + }, + setRejectGetSchedules(flag) { + rejectGetSchedules = flag; + }, + getScheduleUpdates() { + return scheduleKit.subscriber; + }, + getBookDataUpdates(brand) { + return books.get(brand).getDataUpdates(); + }, + getPublicTopics(brand) { + if (brand) { + return books.get(brand).getPublicTopics(); + } + + return { + schedule: makeRecorderTopic('Auction schedule', scheduleKit), + }; + }, + makeDepositInvitation, + ...params, + }), + ); + + const scheduler = await E.when(scheduleKit.recorderP, scheduleRecorder => + makeScheduler( + driver, + timer, + // @ts-expect-error types are correct. How to convince TS? + params, + timerBrand, + scheduleRecorder, + publicFacet.getSubscription(), + ), + ); + + const creatorFacet = makeFarGovernorFacet( + Far('Auctioneer creatorFacet', { + /** + * @param {Issuer} issuer + * @param {Keyword} kwd + */ + async addBrand(issuer, kwd) { + zcf.assertUniqueKeyword(kwd); + !baggage.has(kwd) || + Fail`cannot add brand with keyword ${kwd}. it's in use`; + const { brand } = await zcf.saveIssuer(issuer, kwd); + + const bookId = `book${bookCounter}`; + bookCounter += 1; + const bNode = await E(privateArgs.storageNode).makeChildNode(bookId); + + const newBook = await makeAuctionBook( + brands.Bid, + brand, + priceAuthority, + bNode, + ); + + // These three store.init() calls succeed or fail atomically + deposits.init(brand, harden([])); + books.init(brand, newBook); + brandToKeyword.init(brand, kwd); + }, + /** + * @returns {Promise< + * import('../../src/auction/scheduler.js').FullSchedule + * >} + */ + getSchedule() { + return E(scheduler).getSchedule(); + }, + }), + ); + + return { publicFacet, creatorFacet }; +}; + +/** @typedef {ContractOf} AuctioneerContract */ +/** @typedef {AuctioneerContract['publicFacet']} AuctioneerPublicFacet */ +/** @typedef {AuctioneerContract['creatorFacet']} AuctioneerCreatorFacet */ + +export const AuctionPFShape = M.remotable('Auction Public Facet'); diff --git a/packages/inter-protocol/test/liquidationVisibility/tools.js b/packages/inter-protocol/test/liquidationVisibility/tools.js index da576f86acc..69e4abb16b2 100644 --- a/packages/inter-protocol/test/liquidationVisibility/tools.js +++ b/packages/inter-protocol/test/liquidationVisibility/tools.js @@ -9,14 +9,14 @@ import { TimeMath } from '@agoric/time'; import { subscribeEach } from '@agoric/notifier'; import '../../src/vaultFactory/types.js'; import { withAmountUtils } from '../supports.js'; -import { - getRunFromFaucet, - setupElectorateReserveAndAuction, -} from '../vaultFactory/vaultFactoryUtils.js'; +import { getRunFromFaucet } from '../vaultFactory/vaultFactoryUtils.js'; import { subscriptionTracker, vaultManagerMetricsTracker } from '../metrics.js'; import { startVaultFactory } from '../../src/proposals/econ-behaviors.js'; +import { setupElectorateReserveAndAuction } from './mockTools.js'; -const contractRoots = { +export const BASIS_POINTS = 10000n; + +let contractRoots = { faucet: './test/vaultFactory/faucet.js', VaultFactory: './src/vaultFactory/vaultFactory.js', reserve: './src/reserve/assetReserve.js', @@ -25,7 +25,7 @@ const contractRoots = { const trace = makeTracer('VisibilityTools', true); -export const setupBasics = async zoe => { +export const setupBasics = async (zoe, contractsWrapper) => { const stableIssuer = await E(zoe).getFeeIssuer(); const stableBrand = await E(stableIssuer).getBrand(); @@ -35,6 +35,10 @@ export const setupBasics = async zoe => { makeIssuerKit('aEth', 'nat', { decimalPlaces: 6 }), ); + if (contractsWrapper) { + contractRoots = { ...contractRoots, ...contractsWrapper }; + } + const bundleCache = await unsafeMakeBundleCache('./bundles/'); const bundles = await allValues({ faucet: bundleCache.load(contractRoots.faucet, 'faucet'), From b26f661c4c40c2d681d5beb28ebb14547c999a2b Mon Sep 17 00:00:00 2001 From: JorgeLopes-BytePitch Date: Thu, 1 Feb 2024 12:22:18 +0000 Subject: [PATCH 03/20] chore(liquidationVisibility): #4 add mock makeChainStorageNode --- .../test/liquidationVisibility/mockTools.js | 517 ++++++++++++++++++ 1 file changed, 517 insertions(+) create mode 100644 packages/inter-protocol/test/liquidationVisibility/mockTools.js diff --git a/packages/inter-protocol/test/liquidationVisibility/mockTools.js b/packages/inter-protocol/test/liquidationVisibility/mockTools.js new file mode 100644 index 00000000000..6a5f8a4da6e --- /dev/null +++ b/packages/inter-protocol/test/liquidationVisibility/mockTools.js @@ -0,0 +1,517 @@ +import { E } from '@endo/eventual-send'; +import { M } from '@endo/patterns'; +import { makeIssuerKit, AssetKind } from '@agoric/ertp'; +import { makeTracer } from '@agoric/internal'; +import { buildManualTimer } from '@agoric/swingset-vat/tools/manual-timer.js'; +import '../../src/vaultFactory/types.js'; +import { installPuppetGovernance, produceInstallations } from '../supports.js'; +import { + SECONDS_PER_WEEK, + setupReserve, + startAuctioneer, +} from '../../src/proposals/econ-behaviors.js'; +import '@agoric/zoe/exported.js'; +import { makeManualPriceAuthority } from '@agoric/zoe/tools/manualPriceAuthority.js'; +import { makeScalarBigMapStore } from '@agoric/vat-data/src/index.js'; +import { providePriceAuthorityRegistry } from '@agoric/vats/src/priceAuthorityRegistry.js'; +import { makeScriptedPriceAuthority } from '@agoric/zoe/tools/scriptedPriceAuthority.js'; +import { startEconomicCommittee } from '../../src/proposals/startEconCommittee.js'; +import * as utils from '@agoric/vats/src/core/utils.js'; +import { makePromiseSpace, makeAgoricNamesAccess } from '@agoric/vats'; +import { makeFakeBoard } from '@agoric/vats/tools/board-utils.js'; +import { produceDiagnostics } from '@agoric/vats/src/core/basic-behaviors.js'; +import { Far } from '@endo/far'; +import { unmarshalFromVstorage } from '@agoric/internal/src/marshal.js'; +import { bindAllMethods } from '@agoric/internal/src/method-tools.js'; +import { defaultMarshaller } from '@agoric/internal/src/storage-test-utils.js'; +import { isStreamCell } from '@agoric/internal/src/lib-chainStorage.js'; +import { makeHeapZone } from '@agoric/base-zone/heap.js'; +import { assertPathSegment } from '@agoric/internal/src/lib-chainStorage.js'; +import * as cb from '@agoric/internal/src/callback.js'; + +/** + * This represents a node in an IAVL tree. + * + * The active implementation is x/vstorage, an Agoric extension of the Cosmos + * SDK. + * + * Vstorage is a hierarchical externally-reachable storage structure that + * identifies children by restricted ASCII name and is associated with arbitrary + * string-valued data for each node, defaulting to the empty string. + * + * @typedef {object} StorageNode + * @property {(data: string) => Promise} setValue publishes some data + * @property {() => string} getPath the chain storage path at which the node was + * constructed + * @property {() => Promise} getStoreKey DEPRECATED use getPath + * @property {( + * subPath: string, + * options?: { sequence?: boolean }, + * ) => StorageNode} makeChildNode + */ + +const ChainStorageNodeI = M.interface('StorageNode', { + setValue: M.callWhen(M.string()).returns(), + getPath: M.call().returns(M.string()), + getStoreKey: M.callWhen().returns(M.record()), + makeChildNode: M.call(M.string()) + .optional(M.splitRecord({}, { sequence: M.boolean() }, {})) + .returns(M.or(M.remotable('StorageNode'), M.promise())), +}); + +/** + * Must match the switch in vstorage.go using `vstorageMessage` type + * + * @typedef {| 'get' + * | 'getStoreKey' + * | 'has' + * | 'children' + * | 'entries' + * | 'values' + * | 'size'} StorageGetByPathMessageMethod + * + * @typedef {'set' | 'setWithoutNotify' | 'append'} StorageUpdateEntriesMessageMethod + * + * @typedef {| StorageGetByPathMessageMethod + * | StorageUpdateEntriesMessageMethod} StorageMessageMethod + * + * @typedef {[path: string]} StorageGetByPathMessageArgs + * + * @typedef {[path: string, value?: string | null]} StorageEntry + * + * @typedef {StorageEntry[]} StorageUpdateEntriesMessageArgs + * + * @typedef {| { + * method: StorageGetByPathMessageMethod; + * args: StorageGetByPathMessageArgs; + * } + * | { + * method: StorageUpdateEntriesMessageMethod; + * args: StorageUpdateEntriesMessageArgs; + * }} StorageMessage + */ + +/** @param {import('@agoric/base-zone').Zone} zone */ +const prepareChainStorageNode = zone => { + /** + * Create a storage node for a given backing storage interface and path. + * + * @param {import('@agoric/internal/src/callback.js').Callback< + * (message: StorageMessage) => any + * >} messenger + * a callback for sending a storageMessage object to the storage + * implementation (cf. golang/cosmos/x/vstorage/vstorage.go) + * @param {string} path + * @param {object} [options] + * @param {boolean} [options.sequence] set values with `append` messages + * rather than `set` messages so the backing implementation employs a + * wrapping structure that preserves each value set within a single block. + * Child nodes default to inheriting this option from their parent. + * @returns {StorageNode} + */ + const makeChainStorageNode = zone.exoClass( + 'ChainStorageNode', + ChainStorageNodeI, + /** + * @param {import('@agoric/internal/src/callback.js').Callback< + * (message: StorageMessage) => any + * >} messenger + * @param {string} path + * @param {object} [options] + * @param {boolean} [options.sequence] + */ + (messenger, path, { sequence = false } = {}) => { + assert.typeof(path, 'string'); + assert.typeof(sequence, 'boolean'); + return harden({ path, messenger, sequence }); + }, + { + getPath() { + return this.state.path; + }, + /** + * @deprecated use getPath + * @type {() => Promise} + */ + async getStoreKey() { + const { path, messenger } = this.state; + return cb.callE(messenger, { + method: 'getStoreKey', + args: [path], + }); + }, + + makeChildNode(name, childNodeOptions = {}) { + if (name === '3600') { + console.log('Log: MOCK makeChildNode ... REJECT :'); + return Promise.reject(); + } + + const { sequence, path, messenger } = this.state; + assertPathSegment(name); + const mergedOptions = { sequence, ...childNodeOptions }; + return makeChainStorageNode( + messenger, + `${path}.${name}`, + mergedOptions, + ); + }, + /** @type {(value: string) => Promise} */ + async setValue(value) { + const { sequence, path, messenger } = this.state; + assert.typeof(value, 'string'); + /** @type {StorageEntry} */ + let entry; + if (!sequence && !value) { + entry = [path]; + } else { + entry = [path, value]; + } + await cb.callE(messenger, { + method: sequence ? 'append' : 'set', + args: [entry], + }); + }, + // Possible extensions: + // * getValue() + // * getChildNames() and/or makeChildNodes() + // * getName() + // * recursive delete + // * batch operations + // * local buffering (with end-of-block commit) + }, + ); + return makeChainStorageNode; +}; + +const makeHeapChainStorageNode = prepareChainStorageNode(makeHeapZone()); + +/** + * Create a heap-based root storage node for a given backing function and root + * path. + * + * @param {(message: StorageMessage) => any} handleStorageMessage a function for + * sending a storageMessage object to the storage implementation (cf. + * golang/cosmos/x/vstorage/vstorage.go) + * @param {string} rootPath + * @param {object} [rootOptions] + * @param {boolean} [rootOptions.sequence] employ a wrapping structure that + * preserves each value set within a single block, and default child nodes to + * do the same + */ +function makeChainStorageRoot( + handleStorageMessage, + rootPath, + rootOptions = {}, +) { + const messenger = cb.makeFunctionCallback(handleStorageMessage); + + // Use the heapZone directly. + const rootNode = makeHeapChainStorageNode(messenger, rootPath, rootOptions); + return rootNode; +} + +/** + * A map corresponding with a total function such that `get(key)` is assumed to + * always succeed. + * + * @template K, V + * @typedef {{ [k in Exclude, 'get'>]: Map[k] } & { + * get: (key: K) => V; + * }} TotalMap + */ + +/** + * For testing, creates a chainStorage root node over an in-memory map and + * exposes both the map and the sequence of received messages. The `sequence` + * option defaults to true. + * + * @param {string} rootPath + * @param {Parameters[2]} [rootOptions] + */ +const makeFakeStorageKit = (rootPath, rootOptions) => { + const trace = makeTracer('StorTU', false); + const resolvedOptions = { sequence: true, ...rootOptions }; + /** @type {TotalMap} */ + const data = new Map(); + /** @param {string} prefix */ + const getChildEntries = prefix => { + assert(prefix.endsWith('.')); + const childEntries = new Map(); + for (const [path, value] of data.entries()) { + if (!path.startsWith(prefix)) { + continue; + } + const [segment, ...suffix] = path.slice(prefix.length).split('.'); + if (suffix.length === 0) { + childEntries.set(segment, value); + } else if (!childEntries.has(segment)) { + childEntries.set(segment, null); + } + } + return childEntries; + }; + /** @type {import('@agoric/internal/src/lib-chainStorage.js').StorageMessage[]} */ + const messages = []; + /** @param {import('@agoric/internal/src/lib-chainStorage.js').StorageMessage} message */ + // eslint-disable-next-line consistent-return + const toStorage = message => { + messages.push(message); + switch (message.method) { + case 'getStoreKey': { + const [key] = message.args; + return { storeName: 'swingset', storeSubkey: `fake:${key}` }; + } + case 'get': { + const [key] = message.args; + return data.has(key) ? data.get(key) : null; + } + case 'children': { + const [key] = message.args; + const childEntries = getChildEntries(`${key}.`); + return [...childEntries.keys()]; + } + case 'entries': { + const [key] = message.args; + const childEntries = getChildEntries(`${key}.`); + return [...childEntries.entries()].map(entry => + entry[1] != null ? entry : [entry[0]], + ); + } + case 'set': + case 'setWithoutNotify': { + trace('toStorage set', message); + /** @type {import('@agoric/internal/src/lib-chainStorage.js').StorageEntry[]} */ + const newEntries = message.args; + for (const [key, value] of newEntries) { + if (value != null) { + data.set(key, value); + } else { + data.delete(key); + } + } + break; + } + case 'append': { + trace('toStorage append', message); + /** @type {import('@agoric/internal/src/lib-chainStorage.js').StorageEntry[]} */ + const newEntries = message.args; + for (const [key, value] of newEntries) { + value != null || Fail`attempt to append with no value`; + // In the absence of block boundaries, everything goes in a single StreamCell. + const oldVal = data.get(key); + let streamCell; + if (oldVal != null) { + try { + streamCell = JSON.parse(oldVal); + assert(isStreamCell(streamCell)); + } catch (_err) { + streamCell = undefined; + } + } + if (streamCell === undefined) { + streamCell = { + blockHeight: '0', + values: oldVal != null ? [oldVal] : [], + }; + } + streamCell.values.push(value); + data.set(key, JSON.stringify(streamCell)); + } + break; + } + case 'size': + // Intentionally incorrect because it counts non-child descendants, + // but nevertheless supports a "has children" test. + return [...data.keys()].filter(k => k.startsWith(`${message.args[0]}.`)) + .length; + default: + throw Error(`unsupported method: ${message.method}`); + } + }; + const rootNode = makeChainStorageRoot(toStorage, rootPath, resolvedOptions); + return { + rootNode, + // eslint-disable-next-line object-shorthand + data: /** @type {Map} */ (data), + messages, + toStorage, + }; +}; +harden(makeFakeStorageKit); +/** @typedef {ReturnType} FakeStorageKit */ + +const makeMockChainStorageRoot = () => { + const { rootNode, data } = makeFakeStorageKit('mockChainStorageRoot'); + return Far('mockChainStorage', { + ...bindAllMethods(rootNode), + /** + * Defaults to deserializing slot references into plain Remotable objects + * having the specified interface name (as from `Far(iface)`), but can + * accept a different marshaller for producing Remotables that e.g. embed + * the slot string in their iface name. + * + * @param {string} path + * @param {import('@agoric/internal/src/lib-chainStorage.js').Marshaller} marshaller + * @param {number} [index] + * @returns {unknown} + */ + getBody: (path, marshaller = defaultMarshaller, index = -1) => { + data.size || Fail`no data in storage`; + /** + * @type {ReturnType< + * typeof import('@endo/marshal').makeMarshal + * >['fromCapData']} + */ + const fromCapData = (...args) => + Reflect.apply(marshaller.fromCapData, marshaller, args); + return unmarshalFromVstorage(data, path, fromCapData, index); + }, + keys: () => [...data.keys()], + }); +}; +/** @typedef {ReturnType} MockChainStorageRoot */ + +/** + * @param {any} t + * @param {import('@agoric/time').TimerService} [optTimer] + */ +const setupBootstrap = async (t, optTimer) => { + const trace = makeTracer('PromiseSpace', false); + const space = /** @type {any} */ (makePromiseSpace(trace)); + const { produce, consume } = /** + * @type {import('../../src/proposals/econ-behaviors.js').EconomyBootstrapPowers & + * BootstrapPowers} + */ (space); + + await produceDiagnostics(space); + + const timer = optTimer || buildManualTimer(t.log); + produce.chainTimerService.resolve(timer); + // @ts-ignore + produce.chainStorage.resolve(makeMockChainStorageRoot()); + produce.board.resolve(makeFakeBoard()); + + const { zoe, feeMintAccess, run } = t.context; + produce.zoe.resolve(zoe); + produce.feeMintAccess.resolve(feeMintAccess); + + const { agoricNames, agoricNamesAdmin, spaces } = + await makeAgoricNamesAccess(); + produce.agoricNames.resolve(agoricNames); + produce.agoricNamesAdmin.resolve(agoricNamesAdmin); + + const { brand, issuer } = spaces; + brand.produce.IST.resolve(run.brand); + issuer.produce.IST.resolve(run.issuer); + + return { produce, consume, modules: { utils: { ...utils } }, ...spaces }; +}; + +/** + * @typedef {Record & { + * aeth: IssuerKit & import('../supports.js').AmountUtils; + * run: IssuerKit & import('../supports.js').AmountUtils; + * bundleCache: Awaited< + * ReturnType< + * typeof import('@agoric/swingset-vat/tools/bundleTool.js').unsafeMakeBundleCache + * > + * >; + * rates: VaultManagerParamValues; + * interestTiming: InterestTiming; + * zoe: ZoeService; + * }} Context + */ + +/** + * @param {import('ava').ExecutionContext} t + * @param {IssuerKit<'nat'>} run + * @param {IssuerKit<'nat'>} aeth + * @param {NatValue[] | Ratio} priceOrList + * @param {RelativeTime} quoteInterval + * @param {Amount | undefined} unitAmountIn + * @param {Partial} actionParamArgs + */ +export const setupElectorateReserveAndAuction = async ( + t, + run, + aeth, + priceOrList, + quoteInterval, + unitAmountIn, + { + StartFrequency = SECONDS_PER_WEEK, + DiscountStep = 2000n, + LowestRate = 5500n, + ClockStep = 2n, + StartingRate = 10_500n, + AuctionStartDelay = 10n, + PriceLockPeriod = 3n, + }, +) => { + const { + zoe, + electorateTerms = { committeeName: 'The Cabal', committeeSize: 1 }, + timer, + } = t.context; + + const space = await setupBootstrap(t, timer); + installPuppetGovernance(zoe, space.installation.produce); + produceInstallations(space, t.context.installation); + + await startEconomicCommittee(space, electorateTerms); + await setupReserve(space); + const quoteIssuerKit = makeIssuerKit('quote', AssetKind.SET); + + // priceAuthorityReg is the registry, which contains and multiplexes multiple + // individual priceAuthorities, including aethPriceAuthority. + // priceAuthorityAdmin supports registering more individual priceAuthorities + // with the registry. + /** @type {import('@agoric/zoe/tools/manualPriceAuthority.js').ManualPriceAuthority} */ + // @ts-expect-error scriptedPriceAuthority doesn't actually match this, but manualPriceAuthority does + const aethTestPriceAuthority = Array.isArray(priceOrList) + ? makeScriptedPriceAuthority({ + actualBrandIn: aeth.brand, + actualBrandOut: run.brand, + priceList: priceOrList, + timer, + quoteMint: quoteIssuerKit.mint, + unitAmountIn, + quoteInterval, + }) + : makeManualPriceAuthority({ + actualBrandIn: aeth.brand, + actualBrandOut: run.brand, + initialPrice: priceOrList, + timer, + quoteIssuerKit, + }); + const baggage = makeScalarBigMapStore('baggage'); + const { priceAuthority: priceAuthorityReg, adminFacet: priceAuthorityAdmin } = + providePriceAuthorityRegistry(baggage); + await E(priceAuthorityAdmin).registerPriceAuthority( + aethTestPriceAuthority, + aeth.brand, + run.brand, + ); + + space.produce.priceAuthority.resolve(priceAuthorityReg); + + const auctionParams = { + StartFrequency, + ClockStep, + StartingRate, + LowestRate, + DiscountStep, + AuctionStartDelay, + PriceLockPeriod, + }; + + await startAuctioneer(space, { auctionParams }); + return { + space, + priceAuthority: priceAuthorityReg, + priceAuthorityAdmin, + aethTestPriceAuthority, + }; +}; From 575302cbca7e6ee41dba7ca7ea71f3211211bbbb Mon Sep 17 00:00:00 2001 From: JorgeLopes-BytePitch Date: Thu, 1 Feb 2024 12:23:50 +0000 Subject: [PATCH 04/20] chore(liquidationVisibility): #4 add tests for no vaults and rejected schedule --- .../test-liquidationVisibility.js | 262 +++++++++++++++++- 1 file changed, 260 insertions(+), 2 deletions(-) diff --git a/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js b/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js index 0dd53228a53..649a4b5f9e5 100644 --- a/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js +++ b/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js @@ -62,8 +62,14 @@ test.before(async t => { const { zoe, feeMintAccessP } = await setUpZoeForTest(); const feeMintAccess = await feeMintAccessP; - const { run, aeth, bundleCache, bundles, installation } = - await setupBasics(zoe); + const contractsWrapper = { + auctioneer: './test/liquidationVisibility/auctioneer-contract-wrapper.js', + }; + + const { run, aeth, bundleCache, bundles, installation } = await setupBasics( + zoe, + contractsWrapper, + ); const contextPs = { zoe, @@ -778,3 +784,255 @@ test('liq-result-scenario-3', async t => { node: `vaultFactory.managers.manager0.liquidations.${time.absValue.toString()}`, }); }); + +// Auction starts with no liquidatable vaults +// In this scenario, no child node of liquidation should be created +test('liq-no-vaults', 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, + 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); + 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); + + // 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); + + // the auction will start but no vault will be liquidated + await startAuctionClock(auctioneerKit, manualTimer); + await assertVaultState(t, vaultNotifier, 'active'); + + // Check that no child node with auction start time's name created after the auction started + const vstorageDuringLiquidation = await getDataFromVstorage( + chainStorage, + `vaultFactory.managers.manager0.liquidations`, + ); + t.is(vstorageDuringLiquidation.length, 0); +}); + +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); + + // 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'); + + await E(auctioneerKit.publicFacet).setRejectGetSchedules(true); + + 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, + debtAmount: debtDuringLiquidation, + }, + ], + ], + }); + + await assertVaultState(t, vaultNotifier, 'liquidating'); + await assertVaultCollateral(t, vault, 0n); + 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); + 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()); + await assertVaultCollateral(t, vault, 0n); + 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, + }, + }); +}); + From 4c4facd1bc0c1e8af90d3fb804494e1072457d5e Mon Sep 17 00:00:00 2001 From: anilhelvaci Date: Thu, 1 Feb 2024 15:54:53 +0300 Subject: [PATCH 05/20] chore(internal): create key-value pairs for Promise.allSettled values --- packages/internal/src/utils.js | 12 ++++++++++++ packages/internal/test/test-utils.js | 24 ++++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/packages/internal/src/utils.js b/packages/internal/src/utils.js index 08a1adbdb96..5c99847f58b 100644 --- a/packages/internal/src/utils.js +++ b/packages/internal/src/utils.js @@ -240,6 +240,18 @@ export const allValues = async obj => { return harden(fromEntries(zip(keys(obj), resolved))); }; +/** + * Just like allValues above but doesn't throw when one of the promises reject. + * + * @type + * { >>(obj: T) => Promise<{ [K in keyof T]: Awaited}> } + */ +export const allValuesSettled = async obj => { + const resolved = await Promise.allSettled(values(obj)); + // @ts-expect-error cast + return harden(fromEntries(zip(keys(obj), resolved))); +}; + /** * A tee implementation where all readers are synchronized with each other. * They all consume the source stream in lockstep, and any one returning or diff --git a/packages/internal/test/test-utils.js b/packages/internal/test/test-utils.js index ffced4aa0ed..d342d9c96c9 100644 --- a/packages/internal/test/test-utils.js +++ b/packages/internal/test/test-utils.js @@ -12,6 +12,7 @@ import { forever, deeplyFulfilledObject, synchronizedTee, + allValuesSettled, } from '../src/utils.js'; test('fromUniqueEntries', t => { @@ -264,3 +265,26 @@ test('synchronizedTee - consume synchronized', async t => { t.deepEqual(output1, sourceData.slice(0, i)); t.deepEqual(output2, sourceData.slice(0, i)); }); + +test('allValuesSettled', async t => { + const result = await allValuesSettled({ + promiseOne: Promise.resolve('I am a happy promise'), + promiseTwo: Promise.reject(new Error('I am an upset promise')), + promiseThree: Promise.resolve('I am a happy promise'), + }); + + t.deepEqual(result.promiseOne, { + status: 'fulfilled', + value: 'I am a happy promise', + }); + + t.deepEqual(result.promiseTwo, { + status: 'rejected', + reason: new Error('I am an upset promise'), + }); + + t.deepEqual(result.promiseThree, { + status: 'fulfilled', + value: 'I am a happy promise', + }); +}); From d269efd10e5152c5a39c1a2c3cec20e2d17f17d9 Mon Sep 17 00:00:00 2001 From: anilhelvaci Date: Thu, 1 Feb 2024 16:57:35 +0300 Subject: [PATCH 06/20] fix(internal): make allValuesSettled a mapper for resolved promises and silently handles rejected ones --- packages/internal/src/utils.js | 4 +++- packages/internal/test/test-utils.js | 21 ++++++--------------- 2 files changed, 9 insertions(+), 16 deletions(-) diff --git a/packages/internal/src/utils.js b/packages/internal/src/utils.js index 5c99847f58b..a6cd10aa01c 100644 --- a/packages/internal/src/utils.js +++ b/packages/internal/src/utils.js @@ -248,8 +248,10 @@ export const allValues = async obj => { */ export const allValuesSettled = async obj => { const resolved = await Promise.allSettled(values(obj)); + // @ts-expect-error + const valuesMapped = resolved.map(({ value }) => value); // @ts-expect-error cast - return harden(fromEntries(zip(keys(obj), resolved))); + return harden(fromEntries(zip(keys(obj), valuesMapped))); }; /** diff --git a/packages/internal/test/test-utils.js b/packages/internal/test/test-utils.js index d342d9c96c9..623f743432c 100644 --- a/packages/internal/test/test-utils.js +++ b/packages/internal/test/test-utils.js @@ -268,23 +268,14 @@ test('synchronizedTee - consume synchronized', async t => { test('allValuesSettled', async t => { const result = await allValuesSettled({ - promiseOne: Promise.resolve('I am a happy promise'), + promiseOne: Promise.resolve('I am a happy promise - One'), promiseTwo: Promise.reject(new Error('I am an upset promise')), - promiseThree: Promise.resolve('I am a happy promise'), + promiseThree: Promise.resolve('I am a happy promise - Three'), }); - t.deepEqual(result.promiseOne, { - status: 'fulfilled', - value: 'I am a happy promise', - }); - - t.deepEqual(result.promiseTwo, { - status: 'rejected', - reason: new Error('I am an upset promise'), - }); - - t.deepEqual(result.promiseThree, { - status: 'fulfilled', - value: 'I am a happy promise', + t.deepEqual(result, { + promiseOne: 'I am a happy promise - One', + promiseTwo: undefined, + promiseThree: 'I am a happy promise - Three', }); }); From f5c0f5b8d0d522f784d995183523cfa47870e799 Mon Sep 17 00:00:00 2001 From: anilhelvaci Date: Thu, 1 Feb 2024 17:21:53 +0300 Subject: [PATCH 07/20] fix(internal): fix doc --- packages/internal/src/utils.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/internal/src/utils.js b/packages/internal/src/utils.js index a6cd10aa01c..d069cc9893b 100644 --- a/packages/internal/src/utils.js +++ b/packages/internal/src/utils.js @@ -241,7 +241,8 @@ export const allValues = async obj => { }; /** - * Just like allValues above but doesn't throw when one of the promises reject. + * Just like allValues above but use this when you want to silently handle rejected promises + * and still keep using the values of resolved ones. * * @type * { >>(obj: T) => Promise<{ [K in keyof T]: Awaited}> } From 84087c93226c6b3cb17035703fd3f27e1de46267 Mon Sep 17 00:00:00 2001 From: anilhelvaci Date: Thu, 1 Feb 2024 17:44:19 +0300 Subject: [PATCH 08/20] fix(liquidationVisibility): make sure promises are assigned in key-value fashion, lint fixes. --- .../src/vaultFactory/vaultManager.js | 21 ++++++--------- .../auctioneer-contract-wrapper.js | 3 ++- .../test/liquidationVisibility/mockTools.js | 26 ++++++++++++------- .../test-liquidationVisibility.js | 4 +-- 4 files changed, 28 insertions(+), 26 deletions(-) diff --git a/packages/inter-protocol/src/vaultFactory/vaultManager.js b/packages/inter-protocol/src/vaultFactory/vaultManager.js index 2ce7b9b90b0..46a0b118685 100644 --- a/packages/inter-protocol/src/vaultFactory/vaultManager.js +++ b/packages/inter-protocol/src/vaultFactory/vaultManager.js @@ -26,7 +26,7 @@ import { NotifierShape, RatioShape, } from '@agoric/ertp'; -import { makeTracer } from '@agoric/internal'; +import { allValuesSettled, makeTracer } from "@agoric/internal"; import { makeStoredNotifier, observeNotifier } from '@agoric/notifier'; import { appendToStoredArray } from '@agoric/store/src/stores/store-utils.js'; import { @@ -1385,21 +1385,16 @@ export const prepareVaultManagerKit = ( // 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 }, + const { + makeDeposit: { userSeatPromise, deposited }, liquidationVisibilityWriters, auctionSchedule, - ] = ( - await Promise.allSettled([ - makeDeposit, + } = await allValuesSettled({ + makeDeposit, + liquidationVisibilityWriters: helper.makeLiquidationVisibilityWriters(timestamp), - schedulesP, - ]) - ) - .filter(result => result.status === 'fulfilled') - // @ts-expect-error - .map(result => result.value); + auctionSchedule: schedulesP, + }); void helper.writeLiqVisibility(liquidationVisibilityWriters, [ ['writePreAuction', vaultData], diff --git a/packages/inter-protocol/test/liquidationVisibility/auctioneer-contract-wrapper.js b/packages/inter-protocol/test/liquidationVisibility/auctioneer-contract-wrapper.js index d089e6909b3..9afe215584f 100644 --- a/packages/inter-protocol/test/liquidationVisibility/auctioneer-contract-wrapper.js +++ b/packages/inter-protocol/test/liquidationVisibility/auctioneer-contract-wrapper.js @@ -663,8 +663,9 @@ export const start = async (zcf, privateArgs, baggage) => { getSchedules() { // eslint-disable-next-line no-use-before-define if (rejectGetSchedules === true) { - return Promise.reject('getSchedules promise has failed'); + return Promise.reject(new Error('getSchedules promise has failed')); } else { + // eslint-disable-next-line no-use-before-define return scheduler.getSchedule(); } }, diff --git a/packages/inter-protocol/test/liquidationVisibility/mockTools.js b/packages/inter-protocol/test/liquidationVisibility/mockTools.js index 6a5f8a4da6e..5ccaa737474 100644 --- a/packages/inter-protocol/test/liquidationVisibility/mockTools.js +++ b/packages/inter-protocol/test/liquidationVisibility/mockTools.js @@ -1,21 +1,16 @@ +/* eslint-disable import/no-extraneous-dependencies */ + import { E } from '@endo/eventual-send'; import { M } from '@endo/patterns'; import { makeIssuerKit, AssetKind } from '@agoric/ertp'; import { makeTracer } from '@agoric/internal'; import { buildManualTimer } from '@agoric/swingset-vat/tools/manual-timer.js'; import '../../src/vaultFactory/types.js'; -import { installPuppetGovernance, produceInstallations } from '../supports.js'; -import { - SECONDS_PER_WEEK, - setupReserve, - startAuctioneer, -} from '../../src/proposals/econ-behaviors.js'; import '@agoric/zoe/exported.js'; import { makeManualPriceAuthority } from '@agoric/zoe/tools/manualPriceAuthority.js'; import { makeScalarBigMapStore } from '@agoric/vat-data/src/index.js'; import { providePriceAuthorityRegistry } from '@agoric/vats/src/priceAuthorityRegistry.js'; import { makeScriptedPriceAuthority } from '@agoric/zoe/tools/scriptedPriceAuthority.js'; -import { startEconomicCommittee } from '../../src/proposals/startEconCommittee.js'; import * as utils from '@agoric/vats/src/core/utils.js'; import { makePromiseSpace, makeAgoricNamesAccess } from '@agoric/vats'; import { makeFakeBoard } from '@agoric/vats/tools/board-utils.js'; @@ -24,10 +19,21 @@ import { Far } from '@endo/far'; import { unmarshalFromVstorage } from '@agoric/internal/src/marshal.js'; import { bindAllMethods } from '@agoric/internal/src/method-tools.js'; import { defaultMarshaller } from '@agoric/internal/src/storage-test-utils.js'; -import { isStreamCell } from '@agoric/internal/src/lib-chainStorage.js'; +import { + isStreamCell, + assertPathSegment, +} from '@agoric/internal/src/lib-chainStorage.js'; import { makeHeapZone } from '@agoric/base-zone/heap.js'; -import { assertPathSegment } from '@agoric/internal/src/lib-chainStorage.js'; import * as cb from '@agoric/internal/src/callback.js'; +import { startEconomicCommittee } from '../../src/proposals/startEconCommittee.js'; +import { + SECONDS_PER_WEEK, + setupReserve, + startAuctioneer, +} from '../../src/proposals/econ-behaviors.js'; +import { installPuppetGovernance, produceInstallations } from '../supports.js'; + +const { Fail } = assert; /** * This represents a node in an IAVL tree. @@ -388,7 +394,7 @@ const setupBootstrap = async (t, optTimer) => { const timer = optTimer || buildManualTimer(t.log); produce.chainTimerService.resolve(timer); - // @ts-ignore + // @ts-expect-error produce.chainStorage.resolve(makeMockChainStorageRoot()); produce.board.resolve(makeFakeBoard()); diff --git a/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js b/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js index 649a4b5f9e5..97c1a87d3d7 100644 --- a/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js +++ b/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js @@ -813,7 +813,7 @@ test('liq-no-vaults', async t => { reservePublicFacet, }); - let expectedReserveState = reserveInitialState(run.makeEmpty()); + const expectedReserveState = reserveInitialState(run.makeEmpty()); await assertReserveState(reserveTracker, 'initial', expectedReserveState); await E(reserveCreatorFacet).addIssuer(aeth.issuer, 'Aeth'); @@ -937,7 +937,7 @@ test('liq-rejected-schedule', async t => { await E(auctioneerKit.publicFacet).setRejectGetSchedules(true); - const { startTime, time, endTime } = await startAuctionClock( + const { startTime, time } = await startAuctionClock( auctioneerKit, manualTimer, ); From 6d332b1ecb505e8af26c11543058cba7cfa17767 Mon Sep 17 00:00:00 2001 From: JorgeLopes-BytePitch Date: Thu, 1 Feb 2024 18:06:06 +0000 Subject: [PATCH 09/20] fix(liquidationVisibility): #4 add setBlockMakeChildNode method and update file name --- .../{mockTools.js => mock-setupChainStorage.js} | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) rename packages/inter-protocol/test/liquidationVisibility/{mockTools.js => mock-setupChainStorage.js} (98%) diff --git a/packages/inter-protocol/test/liquidationVisibility/mockTools.js b/packages/inter-protocol/test/liquidationVisibility/mock-setupChainStorage.js similarity index 98% rename from packages/inter-protocol/test/liquidationVisibility/mockTools.js rename to packages/inter-protocol/test/liquidationVisibility/mock-setupChainStorage.js index 6a5f8a4da6e..3a67ce53965 100644 --- a/packages/inter-protocol/test/liquidationVisibility/mockTools.js +++ b/packages/inter-protocol/test/liquidationVisibility/mock-setupChainStorage.js @@ -29,6 +29,13 @@ import { makeHeapZone } from '@agoric/base-zone/heap.js'; import { assertPathSegment } from '@agoric/internal/src/lib-chainStorage.js'; import * as cb from '@agoric/internal/src/callback.js'; +let blockMakeChildNode = ''; + +export const setBlockMakeChildNode = nodeName => { + blockMakeChildNode = nodeName; + return `LOG: blockMakeChildNode set to node ${nodeName}`; +}; + /** * This represents a node in an IAVL tree. * @@ -142,8 +149,8 @@ const prepareChainStorageNode = zone => { }, makeChildNode(name, childNodeOptions = {}) { - if (name === '3600') { - console.log('Log: MOCK makeChildNode ... REJECT :'); + if (blockMakeChildNode === name) { + console.log(`Log: MOCK makeChildNode REJECTED for node ${name}`); return Promise.reject(); } From 67325556aee0b15d0da52a66a091c30493160880 Mon Sep 17 00:00:00 2001 From: JorgeLopes-BytePitch Date: Thu, 1 Feb 2024 18:06:41 +0000 Subject: [PATCH 10/20] fix(liquidationVisibility): #4 update import path --- packages/inter-protocol/test/liquidationVisibility/tools.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/inter-protocol/test/liquidationVisibility/tools.js b/packages/inter-protocol/test/liquidationVisibility/tools.js index 69e4abb16b2..d307df23d08 100644 --- a/packages/inter-protocol/test/liquidationVisibility/tools.js +++ b/packages/inter-protocol/test/liquidationVisibility/tools.js @@ -12,7 +12,7 @@ import { withAmountUtils } from '../supports.js'; import { getRunFromFaucet } from '../vaultFactory/vaultFactoryUtils.js'; import { subscriptionTracker, vaultManagerMetricsTracker } from '../metrics.js'; import { startVaultFactory } from '../../src/proposals/econ-behaviors.js'; -import { setupElectorateReserveAndAuction } from './mockTools.js'; +import { setupElectorateReserveAndAuction } from './mock-setupChainStorage.js'; export const BASIS_POINTS = 10000n; From e4a4c7beafac1fdbd40aeeaf4063d8462285fdba Mon Sep 17 00:00:00 2001 From: JorgeLopes-BytePitch Date: Thu, 1 Feb 2024 18:07:04 +0000 Subject: [PATCH 11/20] chore(liquidationVisibility): #4 add liq-rejected-timestampStorageNode test --- .../test-liquidationVisibility.js | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js b/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js index 649a4b5f9e5..37c29ca7798 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 { setBlockMakeChildNode } from './mock-setupChainStorage.js'; const trace = makeTracer('TestLiquidationVisibility', false); @@ -1036,3 +1037,66 @@ test('liq-rejected-schedule', async t => { }); }); +// Liquidation ends with a happy path +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: { aethCollateralManager }, + aethTestPriceAuthority, + reserveKit: { reserveCreatorFacet }, + auctioneerKit, + chainStorage, + } = services; + + 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); + await bid(t, zoe, auctioneerKit, aeth, bidAmount, desired); + + 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); + + setBlockMakeChildNode('3600'); + + // drop collateral price from 5:1 to 4:1 and liquidate vault + aethTestPriceAuthority.setPrice(makeRatio(40n, run.brand, 10n, aeth.brand)); + + await startAuctionClock(auctioneerKit, manualTimer); + + // Check that {timestamp}.vaults.preAuction values are correct before auction is completed + const vstorageDuringLiquidation = await getDataFromVstorage( + chainStorage, + `vaultFactory.managers.manager0.liquidations`, + ); + t.is(vstorageDuringLiquidation.length, 0); +}); From 031f791e9c5b48646cb2c97fe14e40e646e0ac06 Mon Sep 17 00:00:00 2001 From: JorgeLopes-BytePitch Date: Thu, 1 Feb 2024 18:43:28 +0000 Subject: [PATCH 12/20] fix(liquidationVisibility): #4 fix bug with at makeChildNode --- .../test/liquidationVisibility/mock-setupChainStorage.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/inter-protocol/test/liquidationVisibility/mock-setupChainStorage.js b/packages/inter-protocol/test/liquidationVisibility/mock-setupChainStorage.js index 3a67ce53965..468f64c374b 100644 --- a/packages/inter-protocol/test/liquidationVisibility/mock-setupChainStorage.js +++ b/packages/inter-protocol/test/liquidationVisibility/mock-setupChainStorage.js @@ -151,6 +151,7 @@ const prepareChainStorageNode = zone => { makeChildNode(name, childNodeOptions = {}) { if (blockMakeChildNode === name) { console.log(`Log: MOCK makeChildNode REJECTED for node ${name}`); + setBlockMakeChildNode('') return Promise.reject(); } From 6f25fe35454df3d56d7fe068e4fda95478d0e306 Mon Sep 17 00:00:00 2001 From: JorgeLopes-BytePitch Date: Thu, 1 Feb 2024 18:43:57 +0000 Subject: [PATCH 13/20] fix(liquidationVisibility): #4 update test names and comments --- .../test-liquidationVisibility.js | 48 +++++++------------ 1 file changed, 18 insertions(+), 30 deletions(-) diff --git a/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js b/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js index 37c29ca7798..6b3360d3efb 100644 --- a/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js +++ b/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js @@ -58,7 +58,6 @@ import { setBlockMakeChildNode } from './mock-setupChainStorage.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; @@ -98,8 +97,9 @@ test.before(async t => { trace(t, 'CONTEXT'); }); -// Liquidation ends with a happy path -test('liq-result-scenario-1', async t => { +/* Test liquidation flow 1: + * Auction raises enough IST to cover debt */ +test('liq-flow-1', async t => { const { zoe, run, aeth } = t.context; const manualTimer = buildManualTimer(); @@ -227,21 +227,6 @@ 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, - debtAmount: debtDuringLiquidation, - }, - ], - ], - }); - await assertStorageData({ t, storageRoot: chainStorage, @@ -273,13 +258,10 @@ test('liq-result-scenario-1', async t => { }); }); -// 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('liq-result-scenario-2', async t => { +/* Test liquidation flow 2a: + * Auction does not raise enough to cover IST debt; + * All collateral sold and debt is not covered. */ +test('liq-flow-2a', async t => { const { zoe, aeth, run, rates: defaultRates } = t.context; // Add a vaultManager with 10000 aeth collateral at a 200 aeth/Minted rate @@ -518,7 +500,10 @@ test('liq-result-scenario-2', async t => { }); }); -test('liq-result-scenario-3', async t => { +/* Test liquidation flow 2b: + * Auction does not raise enough to cover IST debt; + * Collateral remains but debt is still not covered by IST raised by auction end */ +test('liq-flow-2b', async t => { const { zoe, aeth, run, rates: defaultRates } = t.context; const rates = harden({ @@ -786,8 +771,8 @@ test('liq-result-scenario-3', async t => { }); }); -// Auction starts with no liquidatable vaults -// In this scenario, no child node of liquidation should be created +/* Auction starts with no liquidatable vaults + * In this scenario, no child node of liquidation should be created */ test('liq-no-vaults', async t => { const { zoe, run, aeth } = t.context; const manualTimer = buildManualTimer(); @@ -865,6 +850,8 @@ 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(); @@ -938,7 +925,7 @@ test('liq-rejected-schedule', async t => { await E(auctioneerKit.publicFacet).setRejectGetSchedules(true); - const { startTime, time, endTime } = await startAuctionClock( + const { startTime, time } = await startAuctionClock( auctioneerKit, manualTimer, ); @@ -1037,7 +1024,8 @@ test('liq-rejected-schedule', async t => { }); }); -// Liquidation ends with a happy path +/* 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(); From 1e582d96248edf805ec97861abb03c15e981ba89 Mon Sep 17 00:00:00 2001 From: JorgeLopes-BytePitch Date: Thu, 1 Feb 2024 18:58:15 +0000 Subject: [PATCH 14/20] fix(liquidationVisibility): #4 lint fix --- packages/inter-protocol/package.json | 4 ++- .../auctioneer-contract-wrapper.js | 4 +-- .../mock-setupChainStorage.js | 26 +++++++++++-------- .../test-liquidationVisibility.js | 2 +- 4 files changed, 21 insertions(+), 15 deletions(-) diff --git a/packages/inter-protocol/package.json b/packages/inter-protocol/package.json index 49cfc77e1c3..920fee4e202 100644 --- a/packages/inter-protocol/package.json +++ b/packages/inter-protocol/package.json @@ -47,7 +47,9 @@ "@endo/marshal": "^1.1.0", "@endo/nat": "^5.0.2", "@endo/promise-kit": "^1.0.2", - "jessie.js": "^0.3.2" + "jessie.js": "^0.3.2", + "@agoric/base-zone": "0.1.1-dev-c4e4693.0", + "@endo/patterns": "1.1.0" }, "devDependencies": { "@agoric/smart-wallet": "^0.5.3", diff --git a/packages/inter-protocol/test/liquidationVisibility/auctioneer-contract-wrapper.js b/packages/inter-protocol/test/liquidationVisibility/auctioneer-contract-wrapper.js index d089e6909b3..794fe6311e6 100644 --- a/packages/inter-protocol/test/liquidationVisibility/auctioneer-contract-wrapper.js +++ b/packages/inter-protocol/test/liquidationVisibility/auctioneer-contract-wrapper.js @@ -661,10 +661,10 @@ export const start = async (zcf, privateArgs, baggage) => { ); }, getSchedules() { - // eslint-disable-next-line no-use-before-define if (rejectGetSchedules === true) { - return Promise.reject('getSchedules promise has failed'); + return Promise.reject(Error('getSchedules was forced to reject')); } else { + // eslint-disable-next-line no-use-before-define return scheduler.getSchedule(); } }, diff --git a/packages/inter-protocol/test/liquidationVisibility/mock-setupChainStorage.js b/packages/inter-protocol/test/liquidationVisibility/mock-setupChainStorage.js index 468f64c374b..9959bb66d6d 100644 --- a/packages/inter-protocol/test/liquidationVisibility/mock-setupChainStorage.js +++ b/packages/inter-protocol/test/liquidationVisibility/mock-setupChainStorage.js @@ -4,18 +4,11 @@ import { makeIssuerKit, AssetKind } from '@agoric/ertp'; import { makeTracer } from '@agoric/internal'; import { buildManualTimer } from '@agoric/swingset-vat/tools/manual-timer.js'; import '../../src/vaultFactory/types.js'; -import { installPuppetGovernance, produceInstallations } from '../supports.js'; -import { - SECONDS_PER_WEEK, - setupReserve, - startAuctioneer, -} from '../../src/proposals/econ-behaviors.js'; import '@agoric/zoe/exported.js'; import { makeManualPriceAuthority } from '@agoric/zoe/tools/manualPriceAuthority.js'; import { makeScalarBigMapStore } from '@agoric/vat-data/src/index.js'; import { providePriceAuthorityRegistry } from '@agoric/vats/src/priceAuthorityRegistry.js'; import { makeScriptedPriceAuthority } from '@agoric/zoe/tools/scriptedPriceAuthority.js'; -import { startEconomicCommittee } from '../../src/proposals/startEconCommittee.js'; import * as utils from '@agoric/vats/src/core/utils.js'; import { makePromiseSpace, makeAgoricNamesAccess } from '@agoric/vats'; import { makeFakeBoard } from '@agoric/vats/tools/board-utils.js'; @@ -24,10 +17,19 @@ import { Far } from '@endo/far'; import { unmarshalFromVstorage } from '@agoric/internal/src/marshal.js'; import { bindAllMethods } from '@agoric/internal/src/method-tools.js'; import { defaultMarshaller } from '@agoric/internal/src/storage-test-utils.js'; -import { isStreamCell } from '@agoric/internal/src/lib-chainStorage.js'; +import { + isStreamCell, + assertPathSegment, +} from '@agoric/internal/src/lib-chainStorage.js'; import { makeHeapZone } from '@agoric/base-zone/heap.js'; -import { assertPathSegment } from '@agoric/internal/src/lib-chainStorage.js'; import * as cb from '@agoric/internal/src/callback.js'; +import { installPuppetGovernance, produceInstallations } from '../supports.js'; +import { startEconomicCommittee } from '../../src/proposals/startEconCommittee.js'; +import { + SECONDS_PER_WEEK, + setupReserve, + startAuctioneer, +} from '../../src/proposals/econ-behaviors.js'; let blockMakeChildNode = ''; @@ -151,7 +153,7 @@ const prepareChainStorageNode = zone => { makeChildNode(name, childNodeOptions = {}) { if (blockMakeChildNode === name) { console.log(`Log: MOCK makeChildNode REJECTED for node ${name}`); - setBlockMakeChildNode('') + setBlockMakeChildNode(''); return Promise.reject(); } @@ -219,6 +221,8 @@ function makeChainStorageRoot( return rootNode; } +const { Fail } = assert; + /** * A map corresponding with a total function such that `get(key)` is assumed to * always succeed. @@ -396,7 +400,7 @@ const setupBootstrap = async (t, optTimer) => { const timer = optTimer || buildManualTimer(t.log); produce.chainTimerService.resolve(timer); - // @ts-ignore + // @ts-expect-error produce.chainStorage.resolve(makeMockChainStorageRoot()); produce.board.resolve(makeFakeBoard()); diff --git a/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js b/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js index 6b3360d3efb..80ac5393906 100644 --- a/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js +++ b/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js @@ -799,7 +799,7 @@ test('liq-no-vaults', async t => { reservePublicFacet, }); - let expectedReserveState = reserveInitialState(run.makeEmpty()); + const expectedReserveState = reserveInitialState(run.makeEmpty()); await assertReserveState(reserveTracker, 'initial', expectedReserveState); await E(reserveCreatorFacet).addIssuer(aeth.issuer, 'Aeth'); From 83b72cd4cf12495d03b8945cfb850f68ebef6af0 Mon Sep 17 00:00:00 2001 From: JorgeLopes-BytePitch Date: Fri, 2 Feb 2024 11:30:49 +0000 Subject: [PATCH 15/20] chore(liquidationVisibility): extend liq-rejected-timestampStorageNode and clean outdated comments --- .../test-liquidationVisibility.js | 78 ++++++++++++------- 1 file changed, 52 insertions(+), 26 deletions(-) diff --git a/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js b/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js index 80ac5393906..9e24abed645 100644 --- a/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js +++ b/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js @@ -331,8 +331,6 @@ test('liq-flow-2a', async t => { 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); @@ -360,8 +358,7 @@ test('liq-flow-2a', async t => { totalDebt: { value: 5250n }, }); - // reduce collateral ///////////////////////////////////// - + // reduce collateral trace(t, 'alice reduce collateral'); // Alice reduce collateral by 300. That leaves her at 700 * 10 > 1.05 * 5000. @@ -385,7 +382,6 @@ test('liq-flow-2a', async t => { totalCollateral: { value: 700n }, }); - // TODO: UNCOMMENT THIS WHEN SOURCE IS READY await assertLiqNodeForAuctionCreated({ t, rootNode: chainStorage, @@ -397,7 +393,7 @@ test('liq-flow-2a', async t => { ); trace(t, 'changed price to 7 RUN/Aeth'); - // A BIDDER places a BID ////////////////////////// + // 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); @@ -420,7 +416,7 @@ test('liq-flow-2a', async t => { // expect Alice to be liquidated because her collateral is too low. await assertVaultState(t, aliceNotifier, Phase.LIQUIDATING); - // TODO: Check vaults.preAuction here + // Check vaults.preAuction here await assertStorageData({ t, storageRoot: chainStorage, @@ -468,7 +464,7 @@ test('liq-flow-2a', async t => { // Bidder bought 800 Aeth await assertBidderPayout(t, bidderSeat, run, 115n, aeth, 700n); - // TODO: Check vaults.postAuction and auctionResults here + // Check vaults.postAuction and auctionResults here await assertStorageData({ t, storageRoot: chainStorage, @@ -476,7 +472,6 @@ test('liq-flow-2a', async t => { expected: [], }); - // FIXME: https://github.com/Jorge-Lopes/agoric-sdk-liquidation-visibility/issues/3#issuecomment-1905488335 await assertStorageData({ t, storageRoot: chainStorage, @@ -493,7 +488,6 @@ test('liq-flow-2a', async t => { }, }); - // TODO: Snapshot here await documentStorageSchema(t, chainStorage, { note: 'Scenario 2 Liquidation Visibility Snapshot', node: `vaultFactory.managers.manager0.liquidations.${now1.absValue.toString()}`, @@ -558,9 +552,7 @@ test('liq-flow-2b', async t => { lockedQuote: null, }); - // ALICE takes out a loan //////////////////////// - - // a loan of 95 with 5% fee produces a debt of 100. + // Create a loan for Alice of 95 with 5% fee produces a debt of 100. const aliceCollateralAmount = aeth.make(15n); const aliceWantMinted = run.make(95n); /** @type {UserSeat} */ @@ -588,7 +580,7 @@ test('liq-flow-2b', async t => { totalCollateral: { value: 15n }, }); - // BOB takes out a loan //////////////////////// + // BOB takes out a loan const bobCollateralAmount = aeth.make(48n); const bobWantMinted = run.make(150n); /** @type {UserSeat} */ @@ -616,7 +608,7 @@ test('liq-flow-2b', async t => { totalCollateral: { value: 63n }, }); - // A BIDDER places a BID ////////////////////////// + // 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); @@ -627,7 +619,7 @@ test('liq-flow-2b', async t => { ); await eventLoopIteration(); - // TODO: assert node not created + // Assert node not created await assertLiqNodeForAuctionCreated({ t, rootNode: chainStorage, @@ -642,7 +634,7 @@ test('liq-flow-2b', async t => { await assertVaultState(t, aliceNotifier, Phase.LIQUIDATING); await assertVaultState(t, bobNotifier, Phase.LIQUIDATING); - // TODO: PreAuction Here + // Check vaults.preAuction here await assertStorageData({ t, storageRoot: chainStorage, @@ -731,7 +723,7 @@ test('liq-flow-2b', async t => { }, }); - // TODO: PostAuction here + // Check vaults.postAuction and auctionResults here await assertStorageData({ t, storageRoot: chainStorage, @@ -747,7 +739,6 @@ test('liq-flow-2b', async t => { ], }); - // TODO: AuctionResults here await assertStorageData({ t, storageRoot: chainStorage, @@ -764,7 +755,6 @@ test('liq-flow-2b', async t => { }, }); - // TODO: Snapshot here await documentStorageSchema(t, chainStorage, { note: 'Scenario 3 Liquidation Visibility Snapshot', node: `vaultFactory.managers.manager0.liquidations.${time.absValue.toString()}`, @@ -1040,13 +1030,19 @@ test('liq-rejected-timestampStorageNode', async t => { ); const { - vaultFactory: { aethCollateralManager }, + vaultFactory: { vaultFactory, aethCollateralManager }, aethTestPriceAuthority, - reserveKit: { reserveCreatorFacet }, + reserveKit: { reserveCreatorFacet, reservePublicFacet }, auctioneerKit, chainStorage, } = services; + const { reserveTracker } = await getMetricTrackers({ + t, + collateralManager: aethCollateralManager, + reservePublicFacet, + }); + await E(reserveCreatorFacet).addIssuer(aeth.issuer, 'Aeth'); const collateralAmount = aeth.make(400n); @@ -1063,9 +1059,12 @@ test('liq-rejected-timestampStorageNode', 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 bidderSeat = await bid(t, zoe, auctioneerKit, aeth, bidAmount, desired); - await legacyOfferResult(vaultSeat); + 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( @@ -1079,12 +1078,39 @@ test('liq-rejected-timestampStorageNode', async t => { // drop collateral price from 5:1 to 4:1 and liquidate vault aethTestPriceAuthority.setPrice(makeRatio(40n, run.brand, 10n, aeth.brand)); - await startAuctionClock(auctioneerKit, manualTimer); + const { startTime } = await startAuctionClock(auctioneerKit, manualTimer); - // Check that {timestamp}.vaults.preAuction values are correct before auction is completed + // 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); + 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); + 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()); + await assertVaultCollateral(t, vault, 0n); + await assertBidderPayout(t, bidderSeat, run, 320n, aeth, 400n); + + await assertReserveState(reserveTracker, 'like', { + allocations: { + Aeth: undefined, + Fee: undefined, + }, + }); }); From 1ab4417c9b8088cf7bfc109e184b69d098817faa Mon Sep 17 00:00:00 2001 From: JorgeLopes-BytePitch Date: Mon, 5 Feb 2024 10:43:48 +0000 Subject: [PATCH 16/20] fix(liquidationVisibility): revert update made to package.json --- packages/inter-protocol/package.json | 4 +--- .../test/liquidationVisibility/mock-setupChainStorage.js | 1 + 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/inter-protocol/package.json b/packages/inter-protocol/package.json index 920fee4e202..49cfc77e1c3 100644 --- a/packages/inter-protocol/package.json +++ b/packages/inter-protocol/package.json @@ -47,9 +47,7 @@ "@endo/marshal": "^1.1.0", "@endo/nat": "^5.0.2", "@endo/promise-kit": "^1.0.2", - "jessie.js": "^0.3.2", - "@agoric/base-zone": "0.1.1-dev-c4e4693.0", - "@endo/patterns": "1.1.0" + "jessie.js": "^0.3.2" }, "devDependencies": { "@agoric/smart-wallet": "^0.5.3", diff --git a/packages/inter-protocol/test/liquidationVisibility/mock-setupChainStorage.js b/packages/inter-protocol/test/liquidationVisibility/mock-setupChainStorage.js index 9959bb66d6d..7acbb6d43ea 100644 --- a/packages/inter-protocol/test/liquidationVisibility/mock-setupChainStorage.js +++ b/packages/inter-protocol/test/liquidationVisibility/mock-setupChainStorage.js @@ -1,3 +1,4 @@ +/* eslint-disable import/no-extraneous-dependencies */ import { E } from '@endo/eventual-send'; import { M } from '@endo/patterns'; import { makeIssuerKit, AssetKind } from '@agoric/ertp'; From 85be4423f46fa826989a23bfe86c712e3398f949 Mon Sep 17 00:00:00 2001 From: JorgeLopes-BytePitch Date: Mon, 5 Feb 2024 10:45:01 +0000 Subject: [PATCH 17/20] chore(liquidationVisibility): update snapshot generated by unit tests --- .../test-liquidationVisibility.js.md | 231 ++++++++++++++++++ .../test-liquidationVisibility.js.snap | Bin 1674 -> 1799 bytes 2 files changed, 231 insertions(+) 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 fa9e2107b77..c8b897457b4 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,237 @@ The actual snapshot is saved in `test-liquidationVisibility.js.snap`. Generated by [AVA](https://avajs.dev). +## liq-flow-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-flow-2a + +> Scenario 2 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: 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, + }, + }, + ], + ], + ], + ] + +## liq-flow-2b + +> Scenario 3 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: 12n, + }, + collateralOffered: { + brand: Object @Alleged: aEth brand {}, + 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, + }, + }, + ], + [ + 'published.vaultFactory.managers.manager0.liquidations.3600.vaults.postAuction', + [ + [ + 'vault1', + { + Collateral: { + brand: Object @Alleged: aEth brand {}, + value: 43n, + }, + phase: 'active', + }, + ], + ], + ], + [ + 'published.vaultFactory.managers.manager0.liquidations.3600.vaults.preAuction', + [ + [ + 'vault0', + { + collateralAmount: { + brand: Object @Alleged: aEth brand {}, + value: 15n, + }, + debtAmount: { + brand: Object @Alleged: ZDEFAULT brand {}, + value: 100n, + }, + }, + ], + [ + 'vault1', + { + collateralAmount: { + brand: Object @Alleged: aEth brand {}, + value: 48n, + }, + debtAmount: { + brand: Object @Alleged: ZDEFAULT brand {}, + value: 158n, + }, + }, + ], + ], + ], + ] + ## liq-result-scenario-1 > Scenario 1 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 bf87f7d5f971b35c0413aa2d82265cb840149df8..72381e1e6b52b4848b8a831026f2858b011e7076 100644 GIT binary patch literal 1799 zcmV+i2l)6wRzV*1r-KM*HX@lScAA|~}Qma_}!&a~gZG|cn(N?Gw6h-}`3Pn(mqVx}~VyRlI z_}#tN?rbi5+mu3NO23!6?B_Q#JF_$Uo$xy}oR;Lw4)yuNL=!cMY?s8HONS+S*V4GC z7KBkXCu{1{hqGBh6CMq{>hlCV`adqnHKp)#IkX1qhdQ|C2Dy7!eRu32U za6S(=i{pk$!6-eW|KyC~QN8HZtMq-1;FQKPwSQ#7~X1~jJ-b8C;6 zTMKfb*HL^E`W(6hb^0)y&_*Z+P58?5(A=NiPBI$js}|eZ+E9gMmfEQ4^d3d{37>T; z+bz&+y$)^;$9)K_a?70=S&{^eD1x+3Rxm9@86yVulZlGdsQ%$$qL8eCv^Uj|jvBI= z^df-*uhV6Mq(QlQf8oj!>-3iwt#1WNtDzx3H4jbHJaE2oAWuRs(`d5| zJqhF#bdE*?HuQHOe?e`{6dJUlaUjc}$7nQULw5st0y;pWv+R8Zatb;}qqA-36(HAi zEI^@iY-j>VFVr8fKAau&!|64qZYWTjI)7Rb3V2Fd^;KH^<(ME9iSdnV;kcM7_BnU4 zzEsQ{{lYX_e)0NSwqQXat!^=sP05~k%4p&hQxjngi$qyfYNy(XGwwRk6z}OZ-@|Xy z3;)CY@J+S{Hyayi%vhZN1j@Uo^%>K5n?6Y*BP6?=7uISy-e}Hv;PBsX{iK4!=HS{e znzz^r+7jXBSJ9z0YuEKXykXGVR!ij*5Bidz#}kD%1*tXBX9gAW?%q zZ;DB#&;?V@6On{8k#-*$$dH1dh%z7NH`LzV{1#Ca)1oA5tsHI1@9>ai;B#v*pnVo_mFmHdKt)7C_-B}yECnL$OmcE?o9JQRA`U0GkqJ# zJCk`xyEFY2i0&h2(;m|9OrtMiMwCrrWXT?Y3gO~DVj3+Bv5?-7@=y>6ZE)}WKqop9F$ zU!uF$6n)a=$R$&z;8Sjgy=N+u8#mVa$HDq<2(uaf#gQx3w=gu5O!FG__5o;^wx;d2 z6}tz>KIjCEF0i3r067O;q0w9G7~b}<>8V>?eNoV}JDL+zQXO=>u0?%Auhz06>}2S3 z;W~!CTE~9JI!@QC12=vV*DgWLv#1;Iu#M3jK<}Zq?}QmoW;~hkWX6-_ov^$UmUqJPPFUW_P4b>emVv^&C(A%#87M3R pg=L_y3>21u!ZJ`;21=dd0n0#P87M3RWtxYv{{VM~#YgT$006FFi~#@u 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 From 48aaa38785e3ce6f103520b57574df1636263b0e Mon Sep 17 00:00:00 2001 From: JorgeLopes-BytePitch Date: Mon, 5 Feb 2024 10:45:12 +0000 Subject: [PATCH 18/20] fix(liquidationVisibility): lint fix --- packages/inter-protocol/src/vaultFactory/vaultManager.js | 2 +- .../liquidationVisibility/test-liquidationVisibility.js | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/inter-protocol/src/vaultFactory/vaultManager.js b/packages/inter-protocol/src/vaultFactory/vaultManager.js index 46a0b118685..512f9235a86 100644 --- a/packages/inter-protocol/src/vaultFactory/vaultManager.js +++ b/packages/inter-protocol/src/vaultFactory/vaultManager.js @@ -26,7 +26,7 @@ import { NotifierShape, RatioShape, } from '@agoric/ertp'; -import { allValuesSettled, makeTracer } from "@agoric/internal"; +import { allValuesSettled, makeTracer } from '@agoric/internal'; import { makeStoredNotifier, observeNotifier } from '@agoric/notifier'; import { appendToStoredArray } from '@agoric/store/src/stores/store-utils.js'; import { diff --git a/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js b/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js index 9e24abed645..729558b2ae4 100644 --- a/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js +++ b/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js @@ -1091,7 +1091,12 @@ test('liq-rejected-timestampStorageNode', async t => { await assertVaultCollateral(t, vault, 0n); await assertVaultCurrentDebt(t, vault, wantMinted); - const currentTime = await setClockAndAdvanceNTimes(manualTimer, 2, startTime, 2n); + const currentTime = await setClockAndAdvanceNTimes( + manualTimer, + 2, + startTime, + 2n, + ); trace(`advanced time to `, currentTime); await assertVaultState(t, vaultNotifier, 'liquidated'); From 8b2dcd8c174056bc25fd13a692773637bf82b143 Mon Sep 17 00:00:00 2001 From: alexanderem49 Date: Thu, 8 Feb 2024 11:39:59 +0400 Subject: [PATCH 19/20] chore(liquidationVisibility) #4 test multiple vaultManagers --- .../test/liquidationVisibility/assertions.js | 12 +- .../mock-setupChainStorage.js | 62 +++- .../test-liquidationVisibility.js.md | 136 +++++++ .../test-liquidationVisibility.js.snap | Bin 1799 -> 1918 bytes .../test-liquidationVisibility.js | 347 +++++++++++++++++- .../test/liquidationVisibility/tools.js | 44 ++- 6 files changed, 562 insertions(+), 39 deletions(-) diff --git a/packages/inter-protocol/test/liquidationVisibility/assertions.js b/packages/inter-protocol/test/liquidationVisibility/assertions.js index 0f4e228a50b..03fbe4d1628 100644 --- a/packages/inter-protocol/test/liquidationVisibility/assertions.js +++ b/packages/inter-protocol/test/liquidationVisibility/assertions.js @@ -68,10 +68,10 @@ export const assertVaultCurrentDebt = async (t, vault, debt) => { ); }; -export const assertVaultCollateral = async (t, vault, collateralValue) => { +export const assertVaultCollateral = async (t, vault, collateralValue, asset) => { const collateralAmount = await E(vault).getCollateralAmount(); - t.deepEqual(collateralAmount, t.context.aeth.make(collateralValue)); + t.deepEqual(collateralAmount, asset.make(collateralValue)); }; export const assertMintedAmount = async (t, vaultSeat, wantMinted) => { @@ -94,11 +94,11 @@ export const assertMintedProceeds = async (t, vaultSeat, wantMinted) => { ); }; -export const assertVaultLocked = async (t, vaultNotifier, lockedValue) => { +export const assertVaultLocked = async (t, vaultNotifier, lockedValue, asset) => { const notification = await E(vaultNotifier).getUpdateSince(); const lockedAmount = notification.value.locked; - t.deepEqual(lockedAmount, t.context.aeth.make(lockedValue)); + t.deepEqual(lockedAmount, asset.make(lockedValue)); }; export const assertVaultDebtSnapshot = async (t, vaultNotifier, wantMinted) => { @@ -139,7 +139,7 @@ export const assertVaultFactoryRewardAllocation = async ( }); }; -export const assertCollateralProceeds = async (t, seat, colWanted) => { +export const assertCollateralProceeds = async (t, seat, colWanted, issuer) => { const { Collateral: withdrawnCol } = await E(seat).getFinalAllocation(); const proceeds4 = await E(seat).getPayouts(); t.deepEqual(withdrawnCol, colWanted); @@ -147,7 +147,7 @@ export const assertCollateralProceeds = async (t, seat, colWanted) => { const collateralWithdrawn = await proceeds4.Collateral; t.truthy( AmountMath.isEqual( - await E(t.context.aeth.issuer).getAmountOf(collateralWithdrawn), + await E(issuer).getAmountOf(collateralWithdrawn), colWanted, ), ); diff --git a/packages/inter-protocol/test/liquidationVisibility/mock-setupChainStorage.js b/packages/inter-protocol/test/liquidationVisibility/mock-setupChainStorage.js index 7acbb6d43ea..2adf2abea17 100644 --- a/packages/inter-protocol/test/liquidationVisibility/mock-setupChainStorage.js +++ b/packages/inter-protocol/test/liquidationVisibility/mock-setupChainStorage.js @@ -444,6 +444,11 @@ const setupBootstrap = async (t, optTimer) => { * @param {RelativeTime} quoteInterval * @param {Amount | undefined} unitAmountIn * @param {Partial} actionParamArgs + * @param {{ + * btc: any; + * btcPrice: Ratio; + * btcAmountIn: any; + * } | undefined} extraAssetKit */ export const setupElectorateReserveAndAuction = async ( t, @@ -461,6 +466,7 @@ export const setupElectorateReserveAndAuction = async ( AuctionStartDelay = 10n, PriceLockPeriod = 3n, }, + extraAssetKit = undefined, ) => { const { zoe, @@ -484,21 +490,40 @@ export const setupElectorateReserveAndAuction = async ( // @ts-expect-error scriptedPriceAuthority doesn't actually match this, but manualPriceAuthority does const aethTestPriceAuthority = Array.isArray(priceOrList) ? makeScriptedPriceAuthority({ - actualBrandIn: aeth.brand, - actualBrandOut: run.brand, - priceList: priceOrList, - timer, - quoteMint: quoteIssuerKit.mint, - unitAmountIn, - quoteInterval, - }) + actualBrandIn: aeth.brand, + actualBrandOut: run.brand, + priceList: priceOrList, + timer, + quoteMint: quoteIssuerKit.mint, + unitAmountIn, + quoteInterval, + }) : makeManualPriceAuthority({ - actualBrandIn: aeth.brand, - actualBrandOut: run.brand, - initialPrice: priceOrList, - timer, - quoteIssuerKit, - }); + actualBrandIn: aeth.brand, + actualBrandOut: run.brand, + initialPrice: priceOrList, + timer, + quoteIssuerKit, + }); + + const abtcTestPriceAuthority = extraAssetKit ? (Array.isArray(extraAssetKit.btcPrice) + ? makeScriptedPriceAuthority({ + actualBrandIn: extraAssetKit.btc.brand, + actualBrandOut: run.brand, + priceList: extraAssetKit.btcPrice, + timer, + quoteMint: quoteIssuerKit.mint, + unitAmountIn: extraAssetKit.btcAmountIn, + quoteInterval, + }) + : makeManualPriceAuthority({ + actualBrandIn: extraAssetKit.btc.brand, + actualBrandOut: run.brand, + initialPrice: extraAssetKit.btcPrice, + timer, + quoteIssuerKit, + })) : undefined; + const baggage = makeScalarBigMapStore('baggage'); const { priceAuthority: priceAuthorityReg, adminFacet: priceAuthorityAdmin } = providePriceAuthorityRegistry(baggage); @@ -508,6 +533,14 @@ export const setupElectorateReserveAndAuction = async ( run.brand, ); + if (extraAssetKit && abtcTestPriceAuthority) { + await E(priceAuthorityAdmin).registerPriceAuthority( + abtcTestPriceAuthority, + extraAssetKit.btc.brand, + run.brand, + ); + } + space.produce.priceAuthority.resolve(priceAuthorityReg); const auctionParams = { @@ -526,5 +559,6 @@ export const setupElectorateReserveAndAuction = async ( priceAuthority: priceAuthorityReg, priceAuthorityAdmin, aethTestPriceAuthority, + abtcTestPriceAuthority }; }; 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 c8b897457b4..6e4e42e80bb 100644 --- a/packages/inter-protocol/test/liquidationVisibility/snapshots/test-liquidationVisibility.js.md +++ b/packages/inter-protocol/test/liquidationVisibility/snapshots/test-liquidationVisibility.js.md @@ -73,6 +73,142 @@ Generated by [AVA](https://avajs.dev). ], ] +## liq-flow-1.1 + +> Scenario 1.1 Liquidation Visibility Snapshot [Aeth] +> 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, + }, + }, + ], + ], + ], + ] + +> Scenario 1.1 Liquidation Visibility Snapshot [Abtc] +> The example below illustrates the schema of the data published there. +> +> See also board marshalling conventions (_to appear_). + + [ + [ + 'published.vaultFactory.managers.manager1.liquidations.3600.auctionResult', + { + collateralForReserve: { + brand: Object @Alleged: aBtc brand {}, + value: 0n, + }, + collateralOffered: { + brand: Object @Alleged: aBtc brand {}, + value: 400n, + }, + collateralRemaining: { + brand: Object @Alleged: aBtc brand {}, + value: 0n, + }, + collateralSold: { + brand: Object @Alleged: aBtc 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.manager1.liquidations.3600.vaults.postAuction', + [], + ], + [ + 'published.vaultFactory.managers.manager1.liquidations.3600.vaults.preAuction', + [ + [ + 'vault0', + { + collateralAmount: { + brand: Object @Alleged: aBtc brand {}, + value: 400n, + }, + debtAmount: { + brand: Object @Alleged: ZDEFAULT brand {}, + value: 1680n, + }, + }, + ], + ], + ], + ] + ## liq-flow-2a > 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 72381e1e6b52b4848b8a831026f2858b011e7076..3d8c83a3e2a56677227643e6e2cfe27c7aeee5a1 100644 GIT binary patch literal 1918 zcmV-^2Z8uORzVsi1U0-NWLdu&)z#^>} ztAy;`O?wM_w`F&)(3tpcY%n1hL?ZEnU?Q3T8Z;qB!30C1(WoELgqUbzh=vb}MuLiH ze0J|`clNG(OR-Qggs0u>KL457nVs3+Z2#Rq7? zhx2*3X&g6@3&zni`ghJaJft6b^&)*=UrwG;>AQW#c3uT#bb+IR$p_n@%X zY7Em2L<8#K@OoPGw)(@|y~EkKB&sQrjEo3bNn0f(G+7yqq=k$yL=?4f&=x_LWkqz5 zC}-42dskaqM93!ePwR<_K;t4?y}9cxSJT=F)MNyxkzrZYdUBN-)omK5x;0m!I)#{P zdn{jDfD68c!#AMMq4Q9S52FdKg;LO%FFy{oz46T?p>e+AVPj(>im;4QBQ>1fLkK_W zvkqmG#WY;~0%#dMPVlvGrnfhcYS4hfqzK&#){bD-TfZe7OK~dHE1!ah6#;FtLGSgy4 zBgu7&oFF8r8t2cM&NpGAD%zkRN&Ry1`a*>%*5c1kTHg{JEr$mD)Hu{o1uz)>77h%^HPe5%ef+4ce^RVR;sKl`WDYKMqj`xfSnI>w?4osG#mZGZ53cFA)>U8liW_}Fz~hNP>jKoAXtBq#RoXzCcxxFSwC`sa4;T)P z+Uzbme%C9Lsz4;BID#k@g)JFNfJk^Ez zQ-mkmh4ipQc*M%vcu|tFs-|FoR9?ebHIc$Yj+Y0GPdWnfMRw4Cq>xA`)JF&}NUF@o zWkE^uX+cp_cnsme$S35?2+0`uz%SUO$-FQ;Oax`q?U6ifN+sHin0WJP#+!^c8E-P) zWW33Elkq0wO~#woDQ~`0;?0O7aq^G!5G}Rw+JYw|%ZdONoiws6SJVGUmg7~&$dZvI zBTGh>j4UUKESG5sMwT}^Sy~5qGRQKnC9W}9=3Z%TDZbK-31xnR{wm{6_C}L&C*w}W zos_i`mb;+!)p6&>3DDn|OZr=6d0g(CK+P{*dCk>w=RQa7JmnG`J7b;ZYq;Ptygl08 zX$rsOGCbDS)#;3$p1@Rksd)!l8-@C+MNiogJ@<`+o~Ll`Gw26r^z?xRgAiXWdMe_b5|w&{Gc*^ z{^)?8VSlywd3O#!bGHUQ6Mtb+?(`SNu`)y2$@Bs&m!L3>aCR~+Fy#HT)lQ~qSX5|- zGnu{x%iHA`(oUwo!lHBJbebXUWEz2`16oa6?PR(ImaWj3Gnu{#%Ms{Q-FQ1-7{7wy z8|Y`sIGZ;93B$k8%z)K6zdY;m0as5+Z!~5KUhOittKAg*fV*JM40;c_jO}(yx*G#d zq&w!W3BFiow<-FV%bsiRFa;lX+wEQBL2lHT>mLUyKOxL8{EMS6SKi#yxG=Ax508Jw4AoiwOJFeh|rU?^(LEj zKP(5K6SVafoAoh}4KF|2tYTJUfPlYX5R^Ngt#?E1(nr}J#13I=DW3T_&>wot8 zpQW9!w3BO|c5>ZpV_4eB^_SOyBqKw%ju6Womb2Wavd=D}G2 E0MsL~=>Px# literal 1799 zcmV+i2l)6wRzV*1r-KM*HX@lScAA|~}Qma_}!&a~gZG|cn(N?Gw6h-}`3Pn(mqVx}~VyRlI z_}#tN?rbi5+mu3NO23!6?B_Q#JF_$Uo$xy}oR;Lw4)yuNL=!cMY?s8HONS+S*V4GC z7KBkXCu{1{hqGBh6CMq{>hlCV`adqnHKp)#IkX1qhdQ|C2Dy7!eRu32U za6S(=i{pk$!6-eW|KyC~QN8HZtMq-1;FQKPwSQ#7~X1~jJ-b8C;6 zTMKfb*HL^E`W(6hb^0)y&_*Z+P58?5(A=NiPBI$js}|eZ+E9gMmfEQ4^d3d{37>T; z+bz&+y$)^;$9)K_a?70=S&{^eD1x+3Rxm9@86yVulZlGdsQ%$$qL8eCv^Uj|jvBI= z^df-*uhV6Mq(QlQf8oj!>-3iwt#1WNtDzx3H4jbHJaE2oAWuRs(`d5| zJqhF#bdE*?HuQHOe?e`{6dJUlaUjc}$7nQULw5st0y;pWv+R8Zatb;}qqA-36(HAi zEI^@iY-j>VFVr8fKAau&!|64qZYWTjI)7Rb3V2Fd^;KH^<(ME9iSdnV;kcM7_BnU4 zzEsQ{{lYX_e)0NSwqQXat!^=sP05~k%4p&hQxjngi$qyfYNy(XGwwRk6z}OZ-@|Xy z3;)CY@J+S{Hyayi%vhZN1j@Uo^%>K5n?6Y*BP6?=7uISy-e}Hv;PBsX{iK4!=HS{e znzz^r+7jXBSJ9z0YuEKXykXGVR!ij*5Bidz#}kD%1*tXBX9gAW?%q zZ;DB#&;?V@6On{8k#-*$$dH1dh%z7NH`LzV{1#Ca)1oA5tsHI1@9>ai;B#v*pnVo_mFmHdKt)7C_-B}yECnL$OmcE?o9JQRA`U0GkqJ# zJCk`xyEFY2i0&h2(;m|9OrtMiMwCrrWXT?Y3gO~DVj3+Bv5?-7@=y>6ZE)}WKqop9F$ zU!uF$6n)a=$R$&z;8Sjgy=N+u8#mVa$HDq<2(uaf#gQx3w=gu5O!FG__5o;^wx;d2 z6}tz>KIjCEF0i3r067O;q0w9G7~b}<>8V>?eNoV}JDL+zQXO=>u0?%Auhz06>}2S3 z;W~!CTE~9JI!@QC12=vV*DgWLv#1;Iu#M3jK<}Zq?}QmoW;~hkWX6-_ov^$UmUqJPPFUW_P4b>emVv^&C(A%#87M3R pg=L_y3>21u!ZJ`;21=dd0n0#P87M3RWtxYv{{VM~#YgT$006FFi~#@u diff --git a/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js b/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js index 729558b2ae4..672c901b1e8 100644 --- a/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js +++ b/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js @@ -66,7 +66,7 @@ test.before(async t => { auctioneer: './test/liquidationVisibility/auctioneer-contract-wrapper.js', }; - const { run, aeth, bundleCache, bundles, installation } = await setupBasics( + const { run, aeth, abtc, bundleCache, bundles, installation } = await setupBasics( zoe, contractsWrapper, ); @@ -91,6 +91,7 @@ test.before(async t => { ...frozenCtx, bundleCache, aeth, + abtc, run, }; @@ -156,7 +157,7 @@ test('liq-flow-1', async t => { await assertVaultState(t, vaultNotifier, 'active'); await assertVaultDebtSnapshot(t, vaultNotifier, wantMinted); await assertMintedAmount(t, vaultSeat, wantMinted); - await assertVaultCollateral(t, vault, 400n); + 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( @@ -199,7 +200,7 @@ test('liq-flow-1', async t => { }); await assertVaultState(t, vaultNotifier, 'liquidating'); - await assertVaultCollateral(t, vault, 0n); + await assertVaultCollateral(t, vault, 0n, aeth); await assertVaultCurrentDebt(t, vault, wantMinted); currentTime = await setClockAndAdvanceNTimes(manualTimer, 2, startTime, 2n); @@ -207,15 +208,15 @@ test('liq-flow-1', async t => { await assertVaultState(t, vaultNotifier, 'liquidated'); await assertVaultSeatExited(t, vaultSeat); - await assertVaultLocked(t, vaultNotifier, 0n); + 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()); - await assertVaultCollateral(t, vault, 0n); + await assertCollateralProceeds(t, closeSeat, aeth.makeEmpty(), aeth.issuer); + await assertVaultCollateral(t, vault, 0n, aeth); await assertBidderPayout(t, bidderSeat, run, 320n, aeth, 400n); expectedReserveState = { @@ -258,6 +259,318 @@ test('liq-flow-1', async t => { }); }); +// assert that vaultId being recorded under liquidations correspond to the correct vaultId under vaults +// test flow with more than one vaultManager +test('liq-flow-1.1', async t => { + const { zoe, run, aeth, abtc } = 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 }, + true + ); + + const { + vaultFactory: { + vaultFactory, + aethCollateralManager, + abtcVaultManager, + abtcCollateralManager, + }, + aethTestPriceAuthority, + abtcTestPriceAuthority, + reserveKit: { reserveCreatorFacet, reservePublicFacet }, + auctioneerKit, + chainStorage, + } = services; + + const { reserveTracker: reserveTrackerAeth } = await getMetricTrackers({ + t, + collateralManager: aethCollateralManager, + reservePublicFacet, + }); + + let expectedReserveStateAeth = reserveInitialState(run.makeEmpty()); + await assertReserveState(reserveTrackerAeth, 'initial', expectedReserveStateAeth); + + const { reserveTracker: reserveTrackerAbtc } = await getMetricTrackers({ + t, + collateralManager: abtcCollateralManager, + reservePublicFacet, + }); + + let expectedReserveStateAbtc = reserveInitialState(run.makeEmpty()); + await assertReserveState(reserveTrackerAbtc, 'initial', expectedReserveStateAbtc); + + await E(reserveCreatorFacet).addIssuer(aeth.issuer, 'Aeth'); + await E(reserveCreatorFacet).addIssuer(abtc.issuer, 'Abtc'); + + const collateralAmountAeth = aeth.make(400n); + const collateralAmountAbtc = abtc.make(400n); + const wantMinted = run.make(1600n); + + const vaultSeatAeth = await openVault({ + t, + cm: aethCollateralManager, + collateralAmount: collateralAmountAeth, + colKeyword: 'aeth', + wantMintedAmount: wantMinted, + }); + const vaultSeatAbtc = await openVault({ + t, + cm: abtcCollateralManager, + collateralAmount: collateralAmountAbtc, + colKeyword: 'abtc', + wantMintedAmount: wantMinted, + }); + + // A bidder places a bid + const bidAmount = run.make(2000n); + const desiredAeth = aeth.make(400n); + const desiredAbtc = abtc.make(400n); + const bidderSeatAeth = await bid(t, zoe, auctioneerKit, aeth, bidAmount, desiredAeth); + const bidderSeatAbtc = await bid(t, zoe, auctioneerKit, abtc, bidAmount, desiredAbtc); + + const { + vault: vaultAeth, + publicNotifiers: { vault: vaultNotifierAeth }, + } = await legacyOfferResult(vaultSeatAeth); + const { + vault: vaultAbtc, + publicNotifiers: { vault: vaultNotifierAbtc }, + } = await legacyOfferResult(vaultSeatAbtc); + + // aeth assertions + await assertVaultCurrentDebt(t, vaultAeth, wantMinted); + await assertVaultState(t, vaultNotifierAeth, 'active'); + await assertVaultDebtSnapshot(t, vaultNotifierAeth, wantMinted); + await assertMintedAmount(t, vaultSeatAeth, wantMinted); + await assertVaultCollateral(t, vaultAeth, 400n, aeth); + + // abtc assertions + await assertVaultCurrentDebt(t, vaultAbtc, wantMinted); + await assertVaultState(t, vaultNotifierAbtc, 'active'); + await assertVaultDebtSnapshot(t, vaultNotifierAbtc, wantMinted); + await assertMintedAmount(t, vaultSeatAbtc, wantMinted); + await assertVaultCollateral(t, vaultAbtc, 400n, abtc); + + // 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)); + abtcTestPriceAuthority.setPrice(makeRatio(40n, run.brand, 10n, abtc.brand)); + + await assertVaultState(t, vaultNotifierAeth, 'active'); + await assertVaultState(t, vaultNotifierAbtc, 'active'); + + const { startTime, time, endTime } = await startAuctionClock( + auctioneerKit, + manualTimer, + ); + let currentTime = time; + + // Check that {timestamp}.vaults.preAuction values are correct before auction is completed + // aeth + const vstorageDuringLiquidationAeth = await getDataFromVstorage( + chainStorage, + `vaultFactory.managers.manager0.liquidations`, + ); + t.not(vstorageDuringLiquidationAeth.length, 0); + const debtDuringLiquidationAeth = await E(vaultAeth).getCurrentDebt(); + await assertStorageData({ + t, + storageRoot: chainStorage, + path: `vaultFactory.managers.manager0.liquidations.${time.absValue.toString()}.vaults.preAuction`, + expected: [ + [ + 'vault0', + { + collateralAmount: collateralAmountAeth, + debtAmount: debtDuringLiquidationAeth, + }, + ], + ], + }); + + // abtc + const vstorageDuringLiquidationAbtc = await getDataFromVstorage( + chainStorage, + `vaultFactory.managers.manager1.liquidations`, + ); + t.not(vstorageDuringLiquidationAbtc.length, 0); + const debtDuringLiquidationAbtc = await E(vaultAbtc).getCurrentDebt(); + await assertStorageData({ + t, + storageRoot: chainStorage, + path: `vaultFactory.managers.manager1.liquidations.${time.absValue.toString()}.vaults.preAuction`, + expected: [ + [ + 'vault0', + { + collateralAmount: collateralAmountAbtc, + debtAmount: debtDuringLiquidationAbtc, + }, + ], + ], + }); + + // aeth + await assertVaultState(t, vaultNotifierAeth, 'liquidating'); + await assertVaultCollateral(t, vaultAeth, 0n, aeth); + await assertVaultCurrentDebt(t, vaultAeth, wantMinted); + + // abtc + await assertVaultState(t, vaultNotifierAbtc, 'liquidating'); + await assertVaultCollateral(t, vaultAbtc, 0n, abtc); + await assertVaultCurrentDebt(t, vaultAbtc, wantMinted); + + currentTime = await setClockAndAdvanceNTimes(manualTimer, 2, startTime, 2n); + trace(`advanced time to `, currentTime); + + // aeth + await assertVaultState(t, vaultNotifierAeth, 'liquidated'); + await assertVaultSeatExited(t, vaultSeatAeth); + await assertVaultLocked(t, vaultNotifierAeth, 0n, aeth); + await assertVaultCurrentDebt(t, vaultAeth, 0n); + await assertVaultFactoryRewardAllocation(t, vaultFactory, 160n); + + // abtc + await assertVaultState(t, vaultNotifierAbtc, 'liquidated'); + await assertVaultSeatExited(t, vaultSeatAbtc); + await assertVaultLocked(t, vaultNotifierAbtc, 0n, abtc); + await assertVaultCurrentDebt(t, vaultAbtc, 0n); + + const closeSeatAeth = await closeVault({ t, vault: vaultAeth }); + await E(closeSeatAeth).getOfferResult(); + + const closeSeatAbtc = await closeVault({ t, vault: vaultAbtc }); + await E(closeSeatAbtc).getOfferResult(); + + // aeth + await assertCollateralProceeds(t, closeSeatAeth, aeth.makeEmpty(), aeth.issuer); + await assertVaultCollateral(t, vaultAeth, 0n, aeth); + await assertBidderPayout(t, bidderSeatAeth, run, 320n, aeth, 400n); + + // abtc + await assertCollateralProceeds(t, closeSeatAbtc, abtc.makeEmpty(), abtc.issuer); + await assertVaultCollateral(t, vaultAbtc, 0n, abtc); + await assertBidderPayout(t, bidderSeatAbtc, run, 320n, abtc, 400n); + + expectedReserveStateAeth = { + allocations: { + Aeth: undefined, + Fee: undefined, + }, + }; + await assertReserveState(reserveTrackerAeth, 'like', expectedReserveStateAeth); + + expectedReserveStateAbtc = { + allocations: { + Abtc: undefined, + Fee: undefined, + }, + }; + await assertReserveState(reserveTrackerAbtc, 'like', expectedReserveStateAbtc); + + // 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: collateralAmountAeth, + debtAmount: debtDuringLiquidationAeth, + }, + ], + ], + }); + + await assertStorageData({ + t, + storageRoot: chainStorage, + path: `vaultFactory.managers.manager1.liquidations.${time.absValue.toString()}.vaults.preAuction`, + expected: [ + [ + 'vault0', + { + collateralAmount: collateralAmountAbtc, + debtAmount: debtDuringLiquidationAbtc, + }, + ], + ], + }); + + await assertStorageData({ + t, + storageRoot: chainStorage, + path: `vaultFactory.managers.manager0.liquidations.${time.absValue.toString()}.vaults.postAuction`, + expected: [], + }); + + await assertStorageData({ + t, + storageRoot: chainStorage, + path: `vaultFactory.managers.manager1.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: collateralAmountAeth, + istTarget: run.make(1680n), + collateralForReserve: aeth.makeEmpty(), + shortfallToReserve: run.makeEmpty(), + mintedProceeds: run.make(1680n), + collateralSold: aeth.make(400n), + collateralRemaining: aeth.makeEmpty(), + endTime, + }, + }); + + await assertStorageData({ + t, + storageRoot: chainStorage, + path: `vaultFactory.managers.manager1.liquidations.${time.absValue.toString()}.auctionResult`, + expected: { + collateralOffered: collateralAmountAbtc, + istTarget: run.make(1680n), + collateralForReserve: abtc.makeEmpty(), + shortfallToReserve: run.makeEmpty(), + mintedProceeds: run.make(1680n), + collateralSold: abtc.make(400n), + collateralRemaining: abtc.makeEmpty(), + endTime, + }, + }); + + // Create snapshot of the storage node + await documentStorageSchema(t, chainStorage, { + note: 'Scenario 1.1 Liquidation Visibility Snapshot [Aeth]', + node: `vaultFactory.managers.manager0.liquidations.${time.absValue.toString()}`, + }); + await documentStorageSchema(t, chainStorage, { + note: 'Scenario 1.1 Liquidation Visibility Snapshot [Abtc]', + node: `vaultFactory.managers.manager1.liquidations.${time.absValue.toString()}`, + }); +}) + /* Test liquidation flow 2a: * Auction does not raise enough to cover IST debt; * All collateral sold and debt is not covered. */ @@ -374,7 +687,7 @@ test('liq-flow-2a', async t => { await E(aliceReduceCollateralSeat).getOfferResult(); trace('alice '); - await assertCollateralProceeds(t, aliceReduceCollateralSeat, aeth.make(300n)); + await assertCollateralProceeds(t, aliceReduceCollateralSeat, aeth.make(300n), aeth.issuer); await assertVaultDebtSnapshot(t, aliceNotifier, aliceWantMinted); trace(t, 'alice reduce collateral'); @@ -819,7 +1132,7 @@ test('liq-no-vaults', async t => { await assertVaultState(t, vaultNotifier, 'active'); await assertVaultDebtSnapshot(t, vaultNotifier, wantMinted); await assertMintedAmount(t, vaultSeat, wantMinted); - await assertVaultCollateral(t, vault, 400n); + 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( @@ -899,7 +1212,7 @@ test('liq-rejected-schedule', async t => { await assertVaultState(t, vaultNotifier, 'active'); await assertVaultDebtSnapshot(t, vaultNotifier, wantMinted); await assertMintedAmount(t, vaultSeat, wantMinted); - await assertVaultCollateral(t, vault, 400n); + 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( @@ -944,7 +1257,7 @@ test('liq-rejected-schedule', async t => { }); await assertVaultState(t, vaultNotifier, 'liquidating'); - await assertVaultCollateral(t, vault, 0n); + await assertVaultCollateral(t, vault, 0n, aeth); await assertVaultCurrentDebt(t, vault, wantMinted); await E(auctioneerKit.publicFacet).setRejectGetSchedules(false); @@ -954,15 +1267,15 @@ test('liq-rejected-schedule', async t => { await assertVaultState(t, vaultNotifier, 'liquidated'); await assertVaultSeatExited(t, vaultSeat); - await assertVaultLocked(t, vaultNotifier, 0n); + 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()); - await assertVaultCollateral(t, vault, 0n); + await assertCollateralProceeds(t, closeSeat, aeth.makeEmpty(), aeth.issuer); + await assertVaultCollateral(t, vault, 0n, aeth); await assertBidderPayout(t, bidderSeat, run, 320n, aeth, 400n); expectedReserveState = { @@ -1088,7 +1401,7 @@ test('liq-rejected-timestampStorageNode', async t => { t.is(vstorageDuringLiquidation.length, 0); await assertVaultState(t, vaultNotifier, 'liquidating'); - await assertVaultCollateral(t, vault, 0n); + await assertVaultCollateral(t, vault, 0n, aeth); await assertVaultCurrentDebt(t, vault, wantMinted); const currentTime = await setClockAndAdvanceNTimes( @@ -1101,15 +1414,15 @@ test('liq-rejected-timestampStorageNode', async t => { await assertVaultState(t, vaultNotifier, 'liquidated'); await assertVaultSeatExited(t, vaultSeat); - await assertVaultLocked(t, vaultNotifier, 0n); + 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()); - await assertVaultCollateral(t, vault, 0n); + 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', { diff --git a/packages/inter-protocol/test/liquidationVisibility/tools.js b/packages/inter-protocol/test/liquidationVisibility/tools.js index d307df23d08..ebdc71759c6 100644 --- a/packages/inter-protocol/test/liquidationVisibility/tools.js +++ b/packages/inter-protocol/test/liquidationVisibility/tools.js @@ -3,7 +3,7 @@ import { makeIssuerKit } from '@agoric/ertp'; import { unsafeMakeBundleCache } from '@agoric/swingset-vat/tools/bundleTool.js'; 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 { makeRatio, makeRatioFromAmounts } from '@agoric/zoe/src/contractSupport/index.js'; import { eventLoopIteration } from '@agoric/internal/src/testing-utils.js'; import { TimeMath } from '@agoric/time'; import { subscribeEach } from '@agoric/notifier'; @@ -34,6 +34,9 @@ export const setupBasics = async (zoe, contractsWrapper) => { const aeth = withAmountUtils( makeIssuerKit('aEth', 'nat', { decimalPlaces: 6 }), ); + const abtc = withAmountUtils( + makeIssuerKit('aBtc', 'nat', { decimalPlaces: 6 }), + ); if (contractsWrapper) { contractRoots = { ...contractRoots, ...contractsWrapper }; @@ -51,6 +54,7 @@ export const setupBasics = async (zoe, contractsWrapper) => { return { run, aeth, + abtc, bundleCache, bundles, installation, @@ -89,11 +93,13 @@ export const setupServices = async ( timer = buildManualTimer(), quoteInterval = 1n, auctionParams = {}, + setupExtraAsset = false, ) => { const { zoe, run, aeth, + abtc, interestTiming, minInitialDebt, referencedUi, @@ -102,7 +108,18 @@ export const setupServices = async ( t.context.timer = timer; - const { space, priceAuthorityAdmin, aethTestPriceAuthority } = + const btcKit = setupExtraAsset ? { + btc: abtc, + btcPrice: makeRatio(50n, run.brand, 10n, abtc.brand), + btcAmountIn: abtc.make(400n), + } : undefined; + + const { + space, + priceAuthorityAdmin, + aethTestPriceAuthority, + abtcTestPriceAuthority, + } = await setupElectorateReserveAndAuction( t, // @ts-expect-error inconsistent types with withAmountUtils @@ -112,6 +129,7 @@ export const setupServices = async ( quoteInterval, unitAmountIn, auctionParams, + btcKit, ); const { @@ -143,6 +161,16 @@ export const setupServices = async ( rates, ); + let abtcVaultManagerP = undefined; + if (setupExtraAsset) { + await eventLoopIteration(); + abtcVaultManagerP = E(vaultFactoryCreatorFacetP).addVaultType( + abtc.issuer, + 'ABtc', + 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 */ @@ -152,9 +180,11 @@ export const setupServices = async ( * VaultFactoryCreatorFacet, * VFC['publicFacet'], * VaultManager, + * VaultManager | undefined, * AuctioneerKit, * ManualPriceAuthority, * CollateralManager, + * CollateralManager | undefined, * chainStorage, * board, * ]} @@ -164,9 +194,11 @@ export const setupServices = async ( vaultFactory, // creator vfPublic, aethVaultManager, + abtcVaultManager, auctioneerKit, priceAuthority, aethCollateralManager, + abtcCollateralManager, chainStorage, board, ] = await Promise.all([ @@ -174,9 +206,11 @@ export const setupServices = async ( vaultFactoryCreatorFacetP, E.get(consume.vaultFactoryKit).publicFacet, aethVaultManagerP, + abtcVaultManagerP || Promise.resolve(undefined), consume.auctioneerKit, /** @type {Promise} */ (consume.priceAuthority), E(aethVaultManagerP).getPublicFacet(), + abtcVaultManagerP ? E(abtcVaultManagerP).getPublicFacet() : Promise.resolve(undefined), consume.chainStorage, consume.board, ]); @@ -198,10 +232,15 @@ export const setupServices = async ( vfPublic, aethVaultManager, aethCollateralManager, + abtcVaultManager, + abtcCollateralManager, }, }; await E(auctioneerKit.creatorFacet).addBrand(aeth.issuer, 'Aeth'); + if (setupExtraAsset) { + await E(auctioneerKit.creatorFacet).addBrand(abtc.issuer, 'ABtc'); + } return { zoe, @@ -215,6 +254,7 @@ export const setupServices = async ( auctioneerKit, priceAuthorityAdmin, aethTestPriceAuthority, + abtcTestPriceAuthority, chainStorage, board, }; From 82fd79df147c163e1030fe991f8c87185694b80b Mon Sep 17 00:00:00 2001 From: alexanderem49 Date: Thu, 8 Feb 2024 15:54:54 +0400 Subject: [PATCH 20/20] fix(liquidationVisibility): #4 lint fix --- .../test/liquidationVisibility/assertions.js | 14 +++- .../mock-setupChainStorage.js | 80 ++++++++++--------- .../test-liquidationVisibility.js | 74 +++++++++++++---- .../test/liquidationVisibility/tools.js | 47 ++++++----- 4 files changed, 139 insertions(+), 76 deletions(-) diff --git a/packages/inter-protocol/test/liquidationVisibility/assertions.js b/packages/inter-protocol/test/liquidationVisibility/assertions.js index 03fbe4d1628..9fe9ef08429 100644 --- a/packages/inter-protocol/test/liquidationVisibility/assertions.js +++ b/packages/inter-protocol/test/liquidationVisibility/assertions.js @@ -68,7 +68,12 @@ export const assertVaultCurrentDebt = async (t, vault, debt) => { ); }; -export const assertVaultCollateral = async (t, vault, collateralValue, asset) => { +export const assertVaultCollateral = async ( + t, + vault, + collateralValue, + asset, +) => { const collateralAmount = await E(vault).getCollateralAmount(); t.deepEqual(collateralAmount, asset.make(collateralValue)); @@ -94,7 +99,12 @@ export const assertMintedProceeds = async (t, vaultSeat, wantMinted) => { ); }; -export const assertVaultLocked = async (t, vaultNotifier, lockedValue, asset) => { +export const assertVaultLocked = async ( + t, + vaultNotifier, + lockedValue, + asset, +) => { const notification = await E(vaultNotifier).getUpdateSince(); const lockedAmount = notification.value.locked; diff --git a/packages/inter-protocol/test/liquidationVisibility/mock-setupChainStorage.js b/packages/inter-protocol/test/liquidationVisibility/mock-setupChainStorage.js index 2adf2abea17..21fe87ea705 100644 --- a/packages/inter-protocol/test/liquidationVisibility/mock-setupChainStorage.js +++ b/packages/inter-protocol/test/liquidationVisibility/mock-setupChainStorage.js @@ -444,11 +444,12 @@ const setupBootstrap = async (t, optTimer) => { * @param {RelativeTime} quoteInterval * @param {Amount | undefined} unitAmountIn * @param {Partial} actionParamArgs - * @param {{ - * btc: any; - * btcPrice: Ratio; - * btcAmountIn: any; - * } | undefined} extraAssetKit + * @param {| { + * btc: any; + * btcPrice: Ratio; + * btcAmountIn: any; + * } + * | undefined} extraAssetKit */ export const setupElectorateReserveAndAuction = async ( t, @@ -490,39 +491,42 @@ export const setupElectorateReserveAndAuction = async ( // @ts-expect-error scriptedPriceAuthority doesn't actually match this, but manualPriceAuthority does const aethTestPriceAuthority = Array.isArray(priceOrList) ? makeScriptedPriceAuthority({ - actualBrandIn: aeth.brand, - actualBrandOut: run.brand, - priceList: priceOrList, - timer, - quoteMint: quoteIssuerKit.mint, - unitAmountIn, - quoteInterval, - }) - : makeManualPriceAuthority({ - actualBrandIn: aeth.brand, - actualBrandOut: run.brand, - initialPrice: priceOrList, - timer, - quoteIssuerKit, - }); - - const abtcTestPriceAuthority = extraAssetKit ? (Array.isArray(extraAssetKit.btcPrice) - ? makeScriptedPriceAuthority({ - actualBrandIn: extraAssetKit.btc.brand, - actualBrandOut: run.brand, - priceList: extraAssetKit.btcPrice, - timer, - quoteMint: quoteIssuerKit.mint, - unitAmountIn: extraAssetKit.btcAmountIn, - quoteInterval, - }) + actualBrandIn: aeth.brand, + actualBrandOut: run.brand, + priceList: priceOrList, + timer, + quoteMint: quoteIssuerKit.mint, + unitAmountIn, + quoteInterval, + }) : makeManualPriceAuthority({ - actualBrandIn: extraAssetKit.btc.brand, - actualBrandOut: run.brand, - initialPrice: extraAssetKit.btcPrice, - timer, - quoteIssuerKit, - })) : undefined; + actualBrandIn: aeth.brand, + actualBrandOut: run.brand, + initialPrice: priceOrList, + timer, + quoteIssuerKit, + }); + + let abtcTestPriceAuthority; + if (extraAssetKit) { + abtcTestPriceAuthority = Array.isArray(extraAssetKit.btcPrice) + ? makeScriptedPriceAuthority({ + actualBrandIn: extraAssetKit.btc.brand, + actualBrandOut: run.brand, + priceList: extraAssetKit.btcPrice, + timer, + quoteMint: quoteIssuerKit.mint, + unitAmountIn: extraAssetKit.btcAmountIn, + quoteInterval, + }) + : makeManualPriceAuthority({ + actualBrandIn: extraAssetKit.btc.brand, + actualBrandOut: run.brand, + initialPrice: extraAssetKit.btcPrice, + timer, + quoteIssuerKit, + }); + } const baggage = makeScalarBigMapStore('baggage'); const { priceAuthority: priceAuthorityReg, adminFacet: priceAuthorityAdmin } = @@ -559,6 +563,6 @@ export const setupElectorateReserveAndAuction = async ( priceAuthority: priceAuthorityReg, priceAuthorityAdmin, aethTestPriceAuthority, - abtcTestPriceAuthority + abtcTestPriceAuthority, }; }; diff --git a/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js b/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js index 672c901b1e8..74e04ddd5fb 100644 --- a/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js +++ b/packages/inter-protocol/test/liquidationVisibility/test-liquidationVisibility.js @@ -66,10 +66,8 @@ test.before(async t => { auctioneer: './test/liquidationVisibility/auctioneer-contract-wrapper.js', }; - const { run, aeth, abtc, bundleCache, bundles, installation } = await setupBasics( - zoe, - contractsWrapper, - ); + const { run, aeth, abtc, bundleCache, bundles, installation } = + await setupBasics(zoe, contractsWrapper); const contextPs = { zoe, @@ -272,14 +270,13 @@ test('liq-flow-1.1', async t => { manualTimer, undefined, { StartFrequency: ONE_HOUR }, - true + true, ); const { vaultFactory: { vaultFactory, aethCollateralManager, - abtcVaultManager, abtcCollateralManager, }, aethTestPriceAuthority, @@ -296,7 +293,11 @@ test('liq-flow-1.1', async t => { }); let expectedReserveStateAeth = reserveInitialState(run.makeEmpty()); - await assertReserveState(reserveTrackerAeth, 'initial', expectedReserveStateAeth); + await assertReserveState( + reserveTrackerAeth, + 'initial', + expectedReserveStateAeth, + ); const { reserveTracker: reserveTrackerAbtc } = await getMetricTrackers({ t, @@ -305,7 +306,11 @@ test('liq-flow-1.1', async t => { }); let expectedReserveStateAbtc = reserveInitialState(run.makeEmpty()); - await assertReserveState(reserveTrackerAbtc, 'initial', expectedReserveStateAbtc); + await assertReserveState( + reserveTrackerAbtc, + 'initial', + expectedReserveStateAbtc, + ); await E(reserveCreatorFacet).addIssuer(aeth.issuer, 'Aeth'); await E(reserveCreatorFacet).addIssuer(abtc.issuer, 'Abtc'); @@ -333,8 +338,22 @@ test('liq-flow-1.1', async t => { const bidAmount = run.make(2000n); const desiredAeth = aeth.make(400n); const desiredAbtc = abtc.make(400n); - const bidderSeatAeth = await bid(t, zoe, auctioneerKit, aeth, bidAmount, desiredAeth); - const bidderSeatAbtc = await bid(t, zoe, auctioneerKit, abtc, bidAmount, desiredAbtc); + const bidderSeatAeth = await bid( + t, + zoe, + auctioneerKit, + aeth, + bidAmount, + desiredAeth, + ); + const bidderSeatAbtc = await bid( + t, + zoe, + auctioneerKit, + abtc, + bidAmount, + desiredAbtc, + ); const { vault: vaultAeth, @@ -457,12 +476,22 @@ test('liq-flow-1.1', async t => { await E(closeSeatAbtc).getOfferResult(); // aeth - await assertCollateralProceeds(t, closeSeatAeth, aeth.makeEmpty(), aeth.issuer); + await assertCollateralProceeds( + t, + closeSeatAeth, + aeth.makeEmpty(), + aeth.issuer, + ); await assertVaultCollateral(t, vaultAeth, 0n, aeth); await assertBidderPayout(t, bidderSeatAeth, run, 320n, aeth, 400n); // abtc - await assertCollateralProceeds(t, closeSeatAbtc, abtc.makeEmpty(), abtc.issuer); + await assertCollateralProceeds( + t, + closeSeatAbtc, + abtc.makeEmpty(), + abtc.issuer, + ); await assertVaultCollateral(t, vaultAbtc, 0n, abtc); await assertBidderPayout(t, bidderSeatAbtc, run, 320n, abtc, 400n); @@ -472,7 +501,11 @@ test('liq-flow-1.1', async t => { Fee: undefined, }, }; - await assertReserveState(reserveTrackerAeth, 'like', expectedReserveStateAeth); + await assertReserveState( + reserveTrackerAeth, + 'like', + expectedReserveStateAeth, + ); expectedReserveStateAbtc = { allocations: { @@ -480,7 +513,11 @@ test('liq-flow-1.1', async t => { Fee: undefined, }, }; - await assertReserveState(reserveTrackerAbtc, 'like', expectedReserveStateAbtc); + await assertReserveState( + reserveTrackerAbtc, + 'like', + expectedReserveStateAbtc, + ); // Check that {timestamp}.vaults.postAuction values are correct after auction is completed await assertStorageData({ @@ -569,7 +606,7 @@ test('liq-flow-1.1', async t => { note: 'Scenario 1.1 Liquidation Visibility Snapshot [Abtc]', node: `vaultFactory.managers.manager1.liquidations.${time.absValue.toString()}`, }); -}) +}); /* Test liquidation flow 2a: * Auction does not raise enough to cover IST debt; @@ -687,7 +724,12 @@ test('liq-flow-2a', async t => { await E(aliceReduceCollateralSeat).getOfferResult(); trace('alice '); - await assertCollateralProceeds(t, aliceReduceCollateralSeat, aeth.make(300n), aeth.issuer); + await assertCollateralProceeds( + t, + aliceReduceCollateralSeat, + aeth.make(300n), + aeth.issuer, + ); await assertVaultDebtSnapshot(t, aliceNotifier, aliceWantMinted); trace(t, 'alice reduce collateral'); diff --git a/packages/inter-protocol/test/liquidationVisibility/tools.js b/packages/inter-protocol/test/liquidationVisibility/tools.js index ebdc71759c6..7ac97e98602 100644 --- a/packages/inter-protocol/test/liquidationVisibility/tools.js +++ b/packages/inter-protocol/test/liquidationVisibility/tools.js @@ -3,7 +3,10 @@ import { makeIssuerKit } from '@agoric/ertp'; import { unsafeMakeBundleCache } from '@agoric/swingset-vat/tools/bundleTool.js'; import { allValues, makeTracer, objectMap } from '@agoric/internal'; import { buildManualTimer } from '@agoric/swingset-vat/tools/manual-timer.js'; -import { makeRatio, makeRatioFromAmounts } from '@agoric/zoe/src/contractSupport/index.js'; +import { + makeRatio, + makeRatioFromAmounts, +} from '@agoric/zoe/src/contractSupport/index.js'; import { eventLoopIteration } from '@agoric/internal/src/testing-utils.js'; import { TimeMath } from '@agoric/time'; import { subscribeEach } from '@agoric/notifier'; @@ -85,6 +88,7 @@ export const setupBasics = async (zoe, contractsWrapper) => { * @param {import('@agoric/time').TimerService} timer * @param {RelativeTime} quoteInterval * @param {Partial} [auctionParams] + * @param setupExtraAsset */ export const setupServices = async ( t, @@ -108,29 +112,30 @@ export const setupServices = async ( t.context.timer = timer; - const btcKit = setupExtraAsset ? { - btc: abtc, - btcPrice: makeRatio(50n, run.brand, 10n, abtc.brand), - btcAmountIn: abtc.make(400n), - } : undefined; + const btcKit = setupExtraAsset + ? { + btc: abtc, + btcPrice: makeRatio(50n, run.brand, 10n, abtc.brand), + btcAmountIn: abtc.make(400n), + } + : undefined; const { space, priceAuthorityAdmin, aethTestPriceAuthority, abtcTestPriceAuthority, - } = - await setupElectorateReserveAndAuction( - t, - // @ts-expect-error inconsistent types with withAmountUtils - run, - aeth, - priceOrList, - quoteInterval, - unitAmountIn, - auctionParams, - btcKit, - ); + } = await setupElectorateReserveAndAuction( + t, + // @ts-expect-error inconsistent types with withAmountUtils + run, + aeth, + priceOrList, + quoteInterval, + unitAmountIn, + auctionParams, + btcKit, + ); const { consume, @@ -161,7 +166,7 @@ export const setupServices = async ( rates, ); - let abtcVaultManagerP = undefined; + let abtcVaultManagerP; if (setupExtraAsset) { await eventLoopIteration(); abtcVaultManagerP = E(vaultFactoryCreatorFacetP).addVaultType( @@ -210,7 +215,9 @@ export const setupServices = async ( consume.auctioneerKit, /** @type {Promise} */ (consume.priceAuthority), E(aethVaultManagerP).getPublicFacet(), - abtcVaultManagerP ? E(abtcVaultManagerP).getPublicFacet() : Promise.resolve(undefined), + abtcVaultManagerP + ? E(abtcVaultManagerP).getPublicFacet() + : Promise.resolve(undefined), consume.chainStorage, consume.board, ]);