Skip to content

Commit

Permalink
feat(vaultManager): expose liquidation metrics (#5393)
Browse files Browse the repository at this point in the history
* refactor(vaultManager): updateMetrics in finish()

* feat(vaultManager): expose metrics on liquidation

* fixup! feat(vaultManager): expose metrics on liquidation

* test: helper for metrics state changes

* deepDifference has bugs; import open source

* fixup test logging

* chore: more detailed error msg

* fixup! test: helper for metrics state changes

* fixup! deepDifference has bugs; import open source

* hack: amount deltas (allow negative)

* fixup! deepDifference has bugs; import open source

* test: metrics subscription testing support

* test: metrics subscription testing support

* document metric properties

* totalDebtTracker

* fixup

* feat(vaultManager): liquidation overage and shortfall metrics

* fixup

* improve metric names

* fixup superfluous updateMetrics()

* cleanup

* updateVaultPriority -> updateVaultAccounting

* validate sums from liquidation vs total debt ever

* comments

* cr

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
  • Loading branch information
turadg and mergify[bot] authored May 25, 2022
1 parent 0df1077 commit 47d4823
Show file tree
Hide file tree
Showing 9 changed files with 408 additions and 79 deletions.
31 changes: 26 additions & 5 deletions packages/run-protocol/src/vaultFactory/liquidation.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,26 @@ import { AmountMath } from '@agoric/ertp';
import { makeRatio, offerTo } from '@agoric/zoe/src/contractSupport/index.js';
import { makeTracer } from '../makeTracer.js';

const trace = makeTracer('LIQ');
const trace = makeTracer('LIQ', false);

/**
*
* @param {Amount<'nat'>} debt
* @param {Amount<'nat'>} proceeds
*/
const discrepancy = (debt, proceeds) => {
if (AmountMath.isGTE(debt, proceeds)) {
return {
overage: AmountMath.makeEmptyFromAmount(debt),
shortfall: AmountMath.subtract(debt, proceeds),
};
} else {
return {
overage: AmountMath.subtract(proceeds, debt),
shortfall: AmountMath.makeEmptyFromAmount(debt),
};
}
};

/**
* Liquidates a Vault, using the strategy to parameterize the particular
Expand All @@ -24,7 +43,6 @@ const trace = makeTracer('LIQ');
* @param {Liquidator} liquidator
* @param {Brand} collateralBrand
* @param {Ratio} penaltyRate
* @returns {Promise<Vault>}
*/
const liquidate = async (
zcf,
Expand Down Expand Up @@ -68,15 +86,18 @@ const liquidate = async (
]);
// NB: all the proceeds from AMM sale are on the vault seat instead of a staging seat

const { shortfall, overage } = discrepancy(debt, proceeds.RUN);

const runToBurn = AmountMath.min(proceeds.RUN, debt);
trace('before burn', { debt, proceeds, runToBurn });
trace('before burn', { debt, proceeds, overage, shortfall, runToBurn });
burnLosses(runToBurn, vaultZcfSeat);

// Accounting complete. Update the vault state.
vault.liquidated(AmountMath.subtract(debt, runToBurn));

// remaining funds are left on the vault for the user to close and claim
return vault;

// for accounting
return { proceeds: proceeds.RUN, overage, shortfall };
};

const liquidationDetailTerms = debtBrand =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ export const makePrioritizedVaults = (reschedulePriceCheck = () => {}) => {
const refreshVaultPriority = (oldDebt, oldCollateral, vaultId) => {
const vault = removeVaultByAttributes(oldDebt, oldCollateral, vaultId);
addVault(vaultId, vault);
return vault;
};

return Far('PrioritizedVaults', {
Expand Down
4 changes: 2 additions & 2 deletions packages/run-protocol/src/vaultFactory/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,8 @@

/**
* @typedef {object} LoanTiming
* @property {RelativeTime} chargingPeriod
* @property {RelativeTime} recordingPeriod
* @property {RelativeTime} chargingPeriod in seconds
* @property {RelativeTime} recordingPeriod in seconds
*/

/**
Expand Down
8 changes: 5 additions & 3 deletions packages/run-protocol/src/vaultFactory/vault.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ const validTransitions = {
* @property {MintAndReallocate} mintAndReallocate
* @property {(amount: Amount, seat: ZCFSeat) => void} burnAndRecord
* @property {() => Ratio} getCompoundedInterest
* @property {(oldDebt: Amount, oldCollateral: Amount, vaultId: VaultId) => void} updateVaultPriority
* @property {(oldDebt: Amount, oldCollateral: Amount, vaultId: VaultId) => void} updateVaultAccounting
* @property {() => import('./vaultManager.js').GovernedParamGetters} getGovernedParams
*/

Expand Down Expand Up @@ -253,7 +253,7 @@ const helperBehavior = {
const { helper } = facets;
helper.updateDebtSnapshot(newDebt);
// update position of this vault in liquidation priority queue
state.manager.updateVaultPriority(
state.manager.updateVaultAccounting(
oldDebt,
oldCollateral,
state.idInManager,
Expand Down Expand Up @@ -292,7 +292,9 @@ const helperBehavior = {
const maxRun = await state.manager.maxDebtFor(collateralAmount);
assert(
AmountMath.isGTE(maxRun, proposedRunDebt, facets.helper.debtBrand()),
X`Requested ${q(proposedRunDebt)} exceeds max ${q(maxRun)}`,
X`Requested ${q(proposedRunDebt)} exceeds max ${q(maxRun)} for ${q(
collateralAmount,
)} collateral`,
);
},

Expand Down
119 changes: 86 additions & 33 deletions packages/run-protocol/src/vaultFactory/vaultManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {
} from '@agoric/notifier';
import { AmountMath } from '@agoric/ertp';

import { defineKindMulti, pickFacet } from '@agoric/vat-data';
import { defineKindMulti, partialAssign, pickFacet } from '@agoric/vat-data';
import { makeVault } from './vault.js';
import { makePrioritizedVaults } from './prioritizedVaults.js';
import { liquidate } from './liquidation.js';
Expand All @@ -33,6 +33,21 @@ const { details: X } = assert;

const trace = makeTracer('VM', false);

// Metrics naming scheme: nouns are present values; past-participles are accumulative.
/**
* @typedef {object} MetricsNotification
*
* @property {number} numVaults present count of vaults
* @property {Amount<'nat'>} totalCollateral present sum of collateral across all vaults
* @property {Amount<'nat'>} totalDebt present sum of debt across all vaults
*
* @property {Amount<'nat'>} totalCollateralSold running sum of collateral sold in liquidation // totalCollateralSold
* @property {Amount<'nat'>} totalOverageReceived running sum of overages, central received greater than debt
* @property {Amount<'nat'>} totalProceedsReceived running sum of central received from liquidation
* @property {Amount<'nat'>} totalShortfallReceived running sum of shortfalls, central received less than debt
* @property {number} numLiquidationsCompleted running count of liquidations
*/

/**
* @typedef {{
* compoundedInterest: Ratio,
Expand All @@ -42,12 +57,6 @@ const trace = makeTracer('VM', false);
* }} AssetState
*
* @typedef {{
* numVaults: number,
* totalCollateral: Amount<'nat'>,
* totalDebt: Amount<'nat'>,
* }} MetricsNotification
*
* @typedef {{
* getChargingPeriod: () => bigint,
* getRecordingPeriod: () => bigint,
* getDebtLimit: () => Amount<'nat'>,
Expand Down Expand Up @@ -82,8 +91,13 @@ const trace = makeTracer('VM', false);
* latestInterestUpdate: bigint,
* liquidator?: Liquidator
* liquidatorInstance?: Instance
* numLiquidationsCompleted: number,
* totalCollateral: Amount<'nat'>,
* totalCollateralSold: Amount<'nat'>,
* totalDebt: Amount<'nat'>,
* totalOverageReceived: Amount<'nat'>,
* totalProceedsReceived: Amount<'nat'>,
* totalShortfallReceived: Amount<'nat'>,
* vaultCounter: number,
* }} MutableState
*/
Expand Down Expand Up @@ -130,18 +144,11 @@ const initState = (
);

const debtBrand = debtMint.getIssuerRecord().brand;
const totalCollateral = AmountMath.makeEmpty(collateralBrand, 'nat');
const totalDebt = AmountMath.makeEmpty(debtBrand, 'nat');
const zeroCollateral = AmountMath.makeEmpty(collateralBrand, 'nat');
const zeroDebt = AmountMath.makeEmpty(debtBrand, 'nat');

const { publication: metricsPublication, subscription: metricsSubscription } =
makeSubscriptionKit();
metricsPublication.updateState(
harden({
numVaults: 0,
totalCollateral,
totalDebt,
}),
);

/** @type {ImmutableState} */
const fixed = {
Expand Down Expand Up @@ -178,14 +185,19 @@ const initState = (
...fixed,
assetNotifier,
assetUpdater,
compoundedInterest,
debtBrand: fixed.debtBrand,
vaultCounter: 0,
latestInterestUpdate,
liquidator: undefined,
liquidatorInstance: undefined,
totalCollateral,
totalDebt,
compoundedInterest,
latestInterestUpdate,
numLiquidationsCompleted: 0,
totalCollateral: zeroCollateral,
totalDebt: zeroDebt,
totalOverageReceived: zeroDebt,
totalProceedsReceived: zeroDebt,
totalCollateralSold: zeroCollateral,
totalShortfallReceived: zeroDebt,
vaultCounter: 0,
};

return state;
Expand Down Expand Up @@ -243,7 +255,7 @@ const helperBehavior = {
},
updateTime,
);
Object.assign(state, stateUpdates);
partialAssign(state, stateUpdates);
facets.helper.assetNotify();
trace('chargeAllVaults complete');
facets.helper.reschedulePriceCheck();
Expand Down Expand Up @@ -272,6 +284,12 @@ const helperBehavior = {
numVaults: state.prioritizedVaults.getCount(),
totalCollateral: state.totalCollateral,
totalDebt: state.totalDebt,

numLiquidationsCompleted: state.numLiquidationsCompleted,
totalCollateralSold: state.totalCollateralSold,
totalOverageReceived: state.totalOverageReceived,
totalProceedsReceived: state.totalProceedsReceived,
totalShortfallReceived: state.totalShortfallReceived,
});
state.metricsPublication.updateState(payload);
},
Expand Down Expand Up @@ -393,6 +411,8 @@ const helperBehavior = {
const { factoryPowers, prioritizedVaults, zcf } = state;
trace('liquidating', vault.getVaultSeat().getProposal());

const collateralPre = vault.getCollateralAmount();

// Start liquidation (vaultState: LIQUIDATING)
const liquidator = state.liquidator;
assert(liquidator);
Expand All @@ -405,9 +425,27 @@ const helperBehavior = {
state.collateralBrand,
factoryPowers.getGovernedParams().getLiquidationPenalty(),
)
.then(() => {
.then(accounting => {
console.log('liquidateAndRemove accounting', accounting);
state.totalProceedsReceived = AmountMath.add(
state.totalProceedsReceived,
accounting.proceeds,
);
state.totalOverageReceived = AmountMath.add(
state.totalOverageReceived,
accounting.overage,
);
state.totalShortfallReceived = AmountMath.add(
state.totalShortfallReceived,
accounting.shortfall,
);
state.totalCollateral = AmountMath.subtract(
state.totalCollateral,
collateralPre,
);
prioritizedVaults.removeVault(key);
trace('liquidated');
state.numLiquidationsCompleted += 1;
facets.helper.updateMetrics();
})
.catch(e => {
Expand Down Expand Up @@ -484,15 +522,32 @@ const managerBehavior = {
*/
getCompoundedInterest: ({ state }) => state.compoundedInterest,
/**
* Called by a vault when its balances change.
*
* @param {MethodContext} context
* @param {Amount<'nat'>} oldDebt
* @param {Amount<'nat'>} oldCollateral
* @param {VaultId} vaultId
*/
updateVaultPriority: ({ state }, oldDebt, oldCollateral, vaultId) => {
const { prioritizedVaults, totalDebt } = state;
prioritizedVaults.refreshVaultPriority(oldDebt, oldCollateral, vaultId);
trace('updateVaultPriority complete', { totalDebt });
updateVaultAccounting: (
{ state, facets },
oldDebt,
oldCollateral,
vaultId,
) => {
const { prioritizedVaults } = state;
const vault = prioritizedVaults.refreshVaultPriority(
oldDebt,
oldCollateral,
vaultId,
);
// totalCollateral += vault's collateral delta (post — pre)
state.totalCollateral = AmountMath.subtract(
AmountMath.add(state.totalCollateral, vault.getCollateralAmount()),
oldCollateral,
);
// debt accounting managed through minting and burning
facets.helper.updateMetrics();
},
};

Expand Down Expand Up @@ -530,7 +585,7 @@ const selfBehavior = {
* @param {MethodContext} context
* @param {ZCFSeat} seat
*/
makeVaultKit: async ({ state, facets: { helper, manager } }, seat) => {
makeVaultKit: async ({ state, facets: { manager } }, seat) => {
const { prioritizedVaults, zcf } = state;
assertProposalShape(seat, {
give: { Collateral: null },
Expand All @@ -549,12 +604,7 @@ const selfBehavior = {
// TODO `await` is allowed until the above ordering is fixed
// eslint-disable-next-line @jessie.js/no-nested-await
const vaultKit = await vault.initVaultKit(seat);
state.totalCollateral = AmountMath.add(
state.totalCollateral,
vaultKit.vault.getCollateralAmount(),
);
seat.exit();
helper.updateMetrics();
return vaultKit;
} catch (err) {
// remove it from prioritizedVaults
Expand Down Expand Up @@ -629,6 +679,9 @@ const selfBehavior = {
const finish = ({ state, facets: { helper } }) => {
state.prioritizedVaults.setRescheduler(helper.reschedulePriceCheck);

// push initial state of metrics
helper.updateMetrics();

observeNotifier(state.periodNotifier, {
updateState: updateTime =>
helper
Expand Down
Loading

0 comments on commit 47d4823

Please sign in to comment.