-
Notifications
You must be signed in to change notification settings - Fork 208
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(vault): econ metrics notifiers #5260
Changes from 13 commits
9cedbdc
5334672
af797d9
b4a5fcf
696b651
8fc22d7
4a0d2d6
a6f5d8b
ae03243
d0c7612
c5bd54d
e4401c1
424776f
d9c0607
024c205
0cb7b4b
b13aba1
58d8555
276c94c
c44cd1e
6a3349e
86dea15
db7194b
db9e406
4c92859
fff80b0
9e30183
d8fba65
74460d2
bbb4ea9
01c3fe6
96c9811
443a932
58f8d9d
3fe00a4
30a8eaf
9dd27d9
905e07c
d1b2344
41fa373
d3bc2d9
fc54589
cf8030d
38219e8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,7 +18,7 @@ import { Far } from '@endo/marshal'; | |
import { AmountMath } from '@agoric/ertp'; | ||
import { assertKeywordName } from '@agoric/zoe/src/cleanProposal.js'; | ||
import { defineKindMulti } from '@agoric/vat-data'; | ||
import { observeIteration } from '@agoric/notifier'; | ||
import { makeNotifierKit, observeIteration } from '@agoric/notifier'; | ||
import { makeVaultManager } from './vaultManager.js'; | ||
import { makeMakeCollectFeesInvitation } from '../collectFees.js'; | ||
import { | ||
|
@@ -31,15 +31,23 @@ import { | |
const { details: X } = assert; | ||
|
||
/** | ||
* @typedef {{ | ||
* collaterals: Brand[], | ||
* penaltyPoolAllocation: AmountKeywordRecord, | ||
* rewardPoolAllocation: AmountKeywordRecord, | ||
* }} MetricsNotification | ||
* | ||
* @typedef {Readonly<{ | ||
* debtMint: ZCFMint<'nat'>, | ||
* collateralTypes: Store<Brand,VaultManager>, | ||
* electionManager: Instance, | ||
* directorParamManager: import('@agoric/governance/src/contractGovernance/typedParamManager').TypedParamManager<{VaultDirectorParams}>, | ||
* directorParamManager: import('@agoric/governance/src/contractGovernance/typedParamManager').TypedParamManager<unknown>, | ||
* mintSeat: ZCFSeat, | ||
* penaltyPoolSeat: ZCFSeat, | ||
* rewardPoolSeat: ZCFSeat, | ||
* vaultParamManagers: Store<Brand, import('./params.js').VaultParamManager>, | ||
* metricsNotifier: Notifier<MetricsNotification> | ||
* metricsUpdater: IterationObserver<MetricsNotification> | ||
* zcf: import('./vaultFactory.js').VaultFactoryZCF, | ||
* }>} ImmutableState | ||
* | ||
|
@@ -70,13 +78,18 @@ const initState = (zcf, directorParamManager, debtMint) => { | |
|
||
const vaultParamManagers = makeScalarMap('brand'); | ||
|
||
const { notifier: metricsNotifier, updater: metricsUpdater } = | ||
makeNotifierKit(); | ||
|
||
return { | ||
collateralTypes, | ||
debtMint, | ||
directorParamManager, | ||
metricsNotifier, | ||
metricsUpdater, | ||
mintSeat, | ||
rewardPoolSeat, | ||
penaltyPoolSeat, | ||
rewardPoolSeat, | ||
vaultParamManagers, | ||
zcf, | ||
}; | ||
|
@@ -154,12 +167,23 @@ const getCollaterals = async ({ state }) => { | |
), | ||
); | ||
}; | ||
|
||
/** | ||
* @param {ImmutableState['directorParamManager']} directorParamManager | ||
*/ | ||
const getLiquidationConfig = directorParamManager => ({ | ||
// @ts-expect-error VaultDirectoryParams isn't right | ||
install: directorParamManager.getLiquidationInstall(), | ||
// @ts-expect-error VaultDirectoryParams isn't right | ||
terms: directorParamManager.getLiquidationTerms(), | ||
}); | ||
|
||
/** | ||
* | ||
* @param {ImmutableState['directorParamManager']} govParams | ||
* @param {VaultManager} vaultManager | ||
* @param {*} oldInstall | ||
* @param {*} oldTerms | ||
*/ | ||
const watchGovernance = (govParams, vaultManager, oldInstall, oldTerms) => { | ||
const subscription = govParams.getSubscription(); | ||
observeIteration(subscription, { | ||
|
@@ -187,7 +211,7 @@ const machineBehavior = { | |
* @param {VaultManagerParamValues} initialParamValues | ||
*/ | ||
addVaultType: async ( | ||
{ state }, | ||
{ state, facets }, | ||
collateralIssuer, | ||
collateralKeyword, | ||
initialParamValues, | ||
|
@@ -252,6 +276,7 @@ const machineBehavior = { | |
} | ||
// TODO add aggregate debt tracking at the vaultFactory level #4482 | ||
// totalDebt = AmountMath.add(totalDebt, toMint); | ||
facets.machine.notifyEcon(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what is "machine"? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That term started here 15322a1#diff-0b7d727a28b84089ac4ebe9b76f8957bb71bfee261640828bad01c4189cbe73fR283 I kept it because it distinguishes between the "creator facet" of the contract and the "limited creator facet" that it can return. I think we should move from "this thing is for what consumer" to "what this thing does or how". (For the same reasons that "public notifiers" is better than "UI notifiers") |
||
}; | ||
|
||
/** | ||
|
@@ -288,6 +313,7 @@ const machineBehavior = { | |
const { install, terms } = getLiquidationConfig(directorParamManager); | ||
await vm.setupLiquidator(install, terms); | ||
watchGovernance(directorParamManager, vm, install, terms); | ||
facets.machine.notifyEcon(); | ||
return vm; | ||
}, | ||
getCollaterals, | ||
|
@@ -303,6 +329,16 @@ const machineBehavior = { | |
}, | ||
/** @param {MethodContext} context */ | ||
getContractGovernor: ({ state }) => state.zcf.getTerms().electionManager, | ||
/** @param {MethodContext} context */ | ||
notifyEcon: ({ state }) => { | ||
/** @type {MetricsNotification} */ | ||
const metrics = harden({ | ||
collaterals: Array.from(state.collateralTypes.keys()), | ||
penaltyPoolAllocation: state.penaltyPoolSeat.getCurrentAllocation(), | ||
rewardPoolAllocation: state.rewardPoolSeat.getCurrentAllocation(), | ||
}); | ||
state.metricsUpdater.updateState(metrics); | ||
}, | ||
|
||
// XXX accessors for tests | ||
/** @param {MethodContext} context */ | ||
|
@@ -356,6 +392,13 @@ const publicBehavior = { | |
/** @type {VaultManager} */ | ||
return collateralTypes.get(brandIn).getPublicFacet(); | ||
}, | ||
/** | ||
* @param {MethodContext} context | ||
*/ | ||
getPublicNotifiers: ({ state }) => { | ||
const { metricsNotifier } = state; | ||
return { metrics: metricsNotifier }; | ||
}, | ||
/** @deprecated use getCollateralManager and then makeVaultInvitation instead */ | ||
makeLoanInvitation: makeVaultInvitation, | ||
/** @deprecated use getCollateralManager and then makeVaultInvitation instead */ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -34,11 +34,15 @@ const trace = makeTracer('VM', false); | |
* compoundedInterest: Ratio, | ||
* interestRate: Ratio, | ||
* latestInterestUpdate: bigint, | ||
* totalDebt: Amount<'nat'>, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This caused a null reference error in the UI https://github.com/Agoric/dapp-treasury/blob/main/ui/src/components/vault/VaultConfigure/VaultConfigure.jsx#L200. I guess typescript/CI didn't catch this somehow... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, I didn't realize that was being used. Yeah, the dapp-treasury repo currently has no type checking Agoric/dapp-treasury#97 |
||
* liquidatorInstance?: Instance, | ||
* }} AssetState */ | ||
|
||
/** | ||
* }} AssetState | ||
* | ||
* @typedef {{ | ||
* numVaults: number, | ||
* totalCollateral: Amount<'nat'>, | ||
* totalDebt: Amount<'nat'>, | ||
* }} MetricsNotification | ||
* | ||
* @typedef {{ | ||
* getChargingPeriod: () => bigint, | ||
* getRecordingPeriod: () => bigint, | ||
|
@@ -70,9 +74,12 @@ const trace = makeTracer('VM', false); | |
* assetNotifier: Notifier<AssetState>, | ||
* assetUpdater: IterationObserver<AssetState>, | ||
* compoundedInterest: Ratio, | ||
* metricsNotifier: Notifier<MetricsNotification>, | ||
* metricsUpdater: IterationObserver<MetricsNotification>, | ||
* latestInterestUpdate: bigint, | ||
* liquidator?: Liquidator | ||
* liquidatorInstance?: Instance | ||
* totalCollateral: Amount<'nat'>, | ||
* totalDebt: Amount<'nat'>, | ||
* vaultCounter: number, | ||
* }} MutableState | ||
|
@@ -138,6 +145,7 @@ const initState = ( | |
zcf, | ||
}; | ||
|
||
const totalCollateral = AmountMath.makeEmpty(fixed.collateralBrand, 'nat'); | ||
const totalDebt = AmountMath.makeEmpty(fixed.debtBrand, 'nat'); | ||
const compoundedInterest = makeRatio(100n, fixed.debtBrand); // starts at 1.0, no interest | ||
// timestamp of most recent update to interest | ||
|
@@ -148,20 +156,30 @@ const initState = ( | |
compoundedInterest, | ||
interestRate: fixed.factoryPowers.getGovernedParams().getInterestRate(), | ||
latestInterestUpdate, | ||
totalDebt, | ||
liquidationInstance: undefined, | ||
}), | ||
); | ||
|
||
const { updater: metricsUpdater, notifier: metricsNotifier } = | ||
makeNotifierKit( | ||
harden({ | ||
numVaults: 0, | ||
totalCollateral, | ||
totalDebt, | ||
}), | ||
); | ||
|
||
/** @type {MutableState & ImmutableState} */ | ||
const state = { | ||
...fixed, | ||
assetNotifier, | ||
assetUpdater, | ||
debtBrand: fixed.debtBrand, | ||
metricsNotifier, | ||
metricsUpdater, | ||
vaultCounter: 0, | ||
liquidator: undefined, | ||
liquidatorInstance: undefined, | ||
totalCollateral, | ||
totalDebt, | ||
compoundedInterest, | ||
latestInterestUpdate, | ||
|
@@ -223,12 +241,13 @@ const helperBehavior = { | |
updateTime, | ||
); | ||
Object.assign(state, stateUpdates); | ||
facets.helper.notify(); | ||
facets.helper.assetNotify(); | ||
trace('chargeAllVaults complete'); | ||
facets.helper.reschedulePriceCheck(); | ||
}, | ||
|
||
notify: ({ state }) => { | ||
/** @param {MethodContext} context */ | ||
assetNotify: ({ state }) => { | ||
const interestRate = state.factoryPowers | ||
.getGovernedParams() | ||
.getInterestRate(); | ||
|
@@ -237,12 +256,23 @@ const helperBehavior = { | |
compoundedInterest: state.compoundedInterest, | ||
interestRate, | ||
latestInterestUpdate: state.latestInterestUpdate, | ||
totalDebt: state.totalDebt, | ||
// XXX move to governance and type as present with null | ||
liquidatorInstance: state.liquidatorInstance, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That should be in governance notifier I would think |
||
}); | ||
state.assetUpdater.updateState(payload); | ||
}, | ||
|
||
/** @param {MethodContext} context */ | ||
metricsNotify: ({ state }) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. notify would conventionally be a fine name, but with notifier/updater the notifier half is about getting notified and the updater half is about sending notifications. I think this needs to be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think Do individual liquidations invoke it? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
/** @type {MetricsNotification} */ | ||
const payload = harden({ | ||
numVaults: state.prioritizedVaults.getCount(), | ||
totalCollateral: state.totalCollateral, | ||
totalDebt: state.totalDebt, | ||
}); | ||
state.metricsUpdater.updateState(payload); | ||
}, | ||
|
||
/** | ||
* When any Vault's debt ratio is higher than the current high-water level, | ||
* call `reschedulePriceCheck()` to request a fresh notification from the | ||
|
@@ -464,7 +494,9 @@ const collateralBehavior = { | |
makeVaultInvitation: ({ state: { zcf }, facets: { self } }) => | ||
zcf.makeInvitation(self.makeVaultKit, 'MakeVault'), | ||
/** @param {MethodContext} context */ | ||
getNotifier: ({ state }) => state.assetNotifier, | ||
getAssetNotifier: ({ state }) => state.assetNotifier, | ||
/** @param {MethodContext} context */ | ||
getPublicNotifiers: ({ state }) => ({ metrics: state.metricsNotifier }), | ||
/** @param {MethodContext} context */ | ||
getCompoundedInterest: ({ state }) => state.compoundedInterest, | ||
}; | ||
|
@@ -491,7 +523,7 @@ const selfBehavior = { | |
* @param {MethodContext} context | ||
* @param {ZCFSeat} seat | ||
*/ | ||
makeVaultKit: async ({ state, facets: { manager } }, seat) => { | ||
makeVaultKit: async ({ state, facets: { helper, manager } }, seat) => { | ||
const { prioritizedVaults, zcf } = state; | ||
assertProposalShape(seat, { | ||
give: { Collateral: null }, | ||
|
@@ -510,7 +542,12 @@ 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.metricsNotify(); | ||
return vaultKit; | ||
} catch (err) { | ||
// remove it from prioritizedVaults | ||
|
@@ -560,7 +597,7 @@ const selfBehavior = { | |
}); | ||
state.liquidatorInstance = instance; | ||
state.liquidator = creatorFacet; | ||
facets.helper.notify(); | ||
facets.helper.assetNotify(); | ||
}, | ||
|
||
/** @param {MethodContext} context */ | ||
|
@@ -636,5 +673,6 @@ const makeVaultManagerKit = defineKindMulti( | |
*/ | ||
export const makeVaultManager = pickFacet(makeVaultManagerKit, 'self'); | ||
|
||
/** @typedef {ReturnType<typeof makeVaultManagerKit>['manager']} VaultKitManager */ | ||
/** @typedef {ReturnType<typeof makeVaultManager>} VaultManager */ | ||
/** @typedef {ReturnType<VaultManager['getPublicFacet']>} CollateralManager */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need a general term for the state snapshot from a notifier. We could remove "UI" from that with "vaultSnapshot"? but "vaultState" isn't terrible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Came up above and proposed
Notification
. I could see that being used for the wrapping object (withvalue
andupdateCount
) but that's so generic as to not need a name.