@agoric/inter-protocol 0.13.2-dev-2354e8d.0 → 0.13.2-dev-f243a2c.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -26,12 +26,14 @@ import {
26
26
  } from '@agoric/ertp';
27
27
  import { makeTracer } from '@agoric/internal';
28
28
  import { makeStoredNotifier, observeNotifier } from '@agoric/notifier';
29
+ import { appendToStoredArray } from '@agoric/store/src/stores/store-utils.js';
29
30
  import {
30
31
  M,
32
+ makeScalarBigMapStore,
33
+ makeScalarBigSetStore,
31
34
  makeScalarMapStore,
32
35
  prepareExoClassKit,
33
- provideDurableMapStore,
34
- provideDurableSetStore,
36
+ provide,
35
37
  } from '@agoric/vat-data';
36
38
  import { TransferPartShape } from '@agoric/zoe/src/contractSupport/atomicTransfer.js';
37
39
  import {
@@ -41,12 +43,12 @@ import {
41
43
  floorMultiplyBy,
42
44
  getAmountIn,
43
45
  getAmountOut,
46
+ makeEphemeraProvider,
44
47
  makeRatio,
45
48
  makeRatioFromAmounts,
46
49
  makeRecorderTopic,
47
50
  multiplyRatios,
48
51
  offerTo,
49
- provideEmptySeat,
50
52
  SubscriberShape,
51
53
  TopicsRecordShape,
52
54
  } from '@agoric/zoe/src/contractSupport/index.js';
@@ -108,6 +110,29 @@ const trace = makeTracer('VM', false);
108
110
  * }} GovernedParamGetters
109
111
  */
110
112
 
113
+ /**
114
+ * @typedef {Readonly<{
115
+ * debtMint: ZCFMint<'nat'>,
116
+ * collateralBrand: Brand<'nat'>,
117
+ * collateralUnit: Amount<'nat'>,
118
+ * descriptionScope: string,
119
+ * startTimeStamp: Timestamp,
120
+ * storageNode: StorageNode,
121
+ * }>} HeldParams
122
+ */
123
+
124
+ /**
125
+ * @typedef {{
126
+ * assetTopicKit: import('@agoric/zoe/src/contractSupport/recorder.js').RecorderKit<AssetState>,
127
+ * debtBrand: Brand<'nat'>,
128
+ * liquidatingVaults: SetStore<Vault>,
129
+ * metricsTopicKit: import('@agoric/zoe/src/contractSupport/recorder.js').RecorderKit<MetricsNotification>,
130
+ * poolIncrementSeat: ZCFSeat,
131
+ * retainedCollateralSeat: ZCFSeat,
132
+ * unsettledVaults: MapStore<string, Vault>,
133
+ * }} ImmutableState
134
+ */
135
+
111
136
  /**
112
137
  * @typedef {{
113
138
  * compoundedInterest: Ratio,
@@ -131,127 +156,81 @@ const trace = makeTracer('VM', false);
131
156
  const quoteAsRatio = quoteAmount =>
132
157
  makeRatioFromAmounts(quoteAmount.amountIn, quoteAmount.amountOut);
133
158
 
159
+ /**
160
+ * @type {(brand: Brand) => {
161
+ * prioritizedVaults: ReturnType<typeof makePrioritizedVaults>,
162
+ * storedQuotesNotifier: import('@agoric/notifier').StoredNotifier<PriceQuote>,
163
+ * storedCollateralQuote: PriceQuote,
164
+ * }}
165
+ */
166
+ // any b/c will be filled after start()
167
+ const collateralEphemera = makeEphemeraProvider(() => /** @type {any} */ ({}));
168
+
134
169
  /**
135
170
  * @param {import('@agoric/ertp').Baggage} baggage
136
- * @param {import('./vaultFactory.js').VaultFactoryZCF} zcf
137
- * @param {ERef<Marshaller>} marshaller
138
- * @param {import('@agoric/zoe/src/contractSupport/recorder.js').MakeRecorderKit} makeRecorderKit
139
- * @param {import('@agoric/zoe/src/contractSupport/recorder.js').MakeERecorderKit} makeERecorderKit
140
- * @param {Readonly<{
141
- * debtMint: ZCFMint<'nat'>,
142
- * collateralBrand: Brand<'nat'>,
143
- * collateralUnit: Amount<'nat'>,
171
+ * @param {{
172
+ * zcf: import('./vaultFactory.js').VaultFactoryZCF,
173
+ * marshaller: ERef<Marshaller>,
174
+ * makeRecorderKit: import('@agoric/zoe/src/contractSupport/recorder.js').MakeRecorderKit,
175
+ * makeERecorderKit: import('@agoric/zoe/src/contractSupport/recorder.js').MakeERecorderKit,
144
176
  * factoryPowers: import('./vaultDirector.js').FactoryPowersFacet,
145
- * descriptionScope: string,
146
- * startTimeStamp: Timestamp,
147
- * storageNode: StorageNode,
148
- * }>} unique per singleton
177
+ * }} powers
149
178
  */
150
179
  export const prepareVaultManagerKit = (
151
180
  baggage,
152
- zcf,
153
- marshaller,
154
- makeRecorderKit,
155
- makeERecorderKit,
156
- {
157
- debtMint,
158
- collateralBrand,
159
- collateralUnit,
160
- descriptionScope,
161
- factoryPowers,
162
- startTimeStamp,
163
- storageNode,
164
- },
181
+ { zcf, marshaller, makeRecorderKit, factoryPowers },
165
182
  ) => {
166
- assert(
167
- storageNode && marshaller,
168
- 'VaultManager missing storageNode or marshaller',
169
- );
170
-
171
183
  const { priceAuthority, timerService, reservePublicFacet } = zcf.getTerms();
172
184
 
173
185
  const makeVault = prepareVault(baggage, makeRecorderKit, zcf);
174
186
 
175
- const periodNotifier = E(timerService).makeNotifier(
176
- 0n,
177
- factoryPowers.getGovernedParams().getChargingPeriod(),
178
- );
179
-
180
- const assetKit = makeRecorderKit(
181
- storageNode,
182
- /** @type {import('@agoric/zoe/src/contractSupport/recorder.js').TypedMatcher<AssetState>} */ (
183
- M.any()
184
- ),
185
- );
186
-
187
- /** @type {MapStore<string, Vault>} */
188
- const unsettledVaults = provideDurableMapStore(baggage, 'orderedVaultStore');
189
-
190
- const debtBrand = debtMint.getIssuerRecord().brand;
191
- const zeroCollateral = AmountMath.makeEmpty(collateralBrand, 'nat');
192
- const zeroDebt = AmountMath.makeEmpty(debtBrand, 'nat');
193
-
194
- const metricsTopicKit = makeERecorderKit(
195
- E(storageNode).makeChildNode('metrics'),
196
- /** @type {import('@agoric/zoe/src/contractSupport/recorder.js').TypedMatcher<MetricsNotification>} */ (
197
- M.any()
198
- ),
199
- );
200
-
201
- const quoteNotifier = E(priceAuthority).makeQuoteNotifier(
202
- collateralUnit,
203
- debtBrand,
204
- );
205
- const storedQuotesNotifier = makeStoredNotifier(
206
- quoteNotifier,
207
- E(storageNode).makeChildNode('quotes'),
208
- marshaller,
209
- );
210
- /** @type {PriceQuote?} */
211
- let storedCollateralQuote = null;
212
- void observeNotifier(quoteNotifier, {
213
- updateState(value) {
214
- storedCollateralQuote = value;
215
- },
216
- fail(reason) {
217
- console.error('quoteNotifier failed to iterate', reason);
218
- },
219
- });
220
-
221
- const prioritizedVaults = makePrioritizedVaults(unsettledVaults);
222
-
223
187
  /**
224
- * Vaults that have been sent for liquidation. When we get proceeds (or lack
225
- * thereof) back from the liquidator, we will allocate them among the vaults.
226
- *
227
- * @type {SetStore<Vault>}
188
+ * @param {HeldParams & { metricsStorageNode: StorageNode }} params
189
+ * @returns {HeldParams & ImmutableState & MutableState}
228
190
  */
229
- const liquidatingVaults = provideDurableSetStore(
230
- baggage,
231
- 'liquidatingVaults',
232
- );
191
+ const initState = params => {
192
+ const {
193
+ debtMint,
194
+ collateralBrand,
195
+ metricsStorageNode,
196
+ startTimeStamp,
197
+ storageNode,
198
+ } = params;
199
+ const debtBrand = debtMint.getIssuerRecord().brand;
200
+
201
+ /** @type {ImmutableState} */
202
+ const immutable = {
203
+ debtBrand,
204
+ poolIncrementSeat: zcf.makeEmptySeatKit().zcfSeat,
205
+
206
+ /**
207
+ * Vaults that have been sent for liquidation. When we get proceeds (or lack
208
+ * thereof) back from the liquidator, we will allocate them among the vaults.
209
+ *
210
+ * @type {SetStore<Vault>}
211
+ */
212
+ liquidatingVaults: makeScalarBigSetStore('liquidatingVaults', {
213
+ durable: true,
214
+ }),
233
215
 
234
- const poolIncrementSeat = provideEmptySeat(zcf, baggage, 'pool increment');
216
+ assetTopicKit: makeRecorderKit(storageNode),
235
217
 
236
- // TODO(#7074) not used while liquidation is disabled. Reinstate with #7074
237
- const retainedCollateralSeat = provideEmptySeat(
238
- zcf,
239
- baggage,
240
- 'retained collateral',
241
- );
218
+ metricsTopicKit: makeRecorderKit(metricsStorageNode),
242
219
 
243
- const topics = harden({
244
- asset: makeRecorderTopic('State of the assets managed', assetKit),
245
- metrics: makeRecorderTopic('Vault Factory metrics', metricsTopicKit),
246
- });
220
+ // TODO(#7074) not used while liquidation is disabled. Reinstate with #7074
221
+ retainedCollateralSeat: zcf.makeEmptySeatKit().zcfSeat,
247
222
 
248
- /**
249
- * This class is a singleton kind so initState will be called only once per prepare.
250
- *
251
- * @returns {MutableState}
252
- */
253
- const initState = () => {
254
- return {
223
+ unsettledVaults: makeScalarBigMapStore('orderedVaultStore', {
224
+ durable: true,
225
+ }),
226
+ };
227
+
228
+ const zeroCollateral = AmountMath.makeEmpty(collateralBrand, 'nat');
229
+ const zeroDebt = AmountMath.makeEmpty(debtBrand, 'nat');
230
+
231
+ return harden({
232
+ ...params,
233
+ ...immutable,
255
234
  compoundedInterest: makeRatio(100n, debtBrand), // starts at 1.0, no interest
256
235
  latestInterestUpdate: startTimeStamp,
257
236
  numLiquidationsCompleted: 0,
@@ -266,11 +245,10 @@ export const prepareVaultManagerKit = (
266
245
  totalShortfallReceived: zeroDebt,
267
246
  vaultCounter: 0,
268
247
  lockedQuote: undefined,
269
- };
248
+ });
270
249
  };
271
250
 
272
- // TODO find a way to not have to indent a level deeper than defineDurableExoClassKit does
273
- return prepareExoClassKit(
251
+ const makeVaultManagerKitInternal = prepareExoClassKit(
274
252
  baggage,
275
253
  'VaultManagerKit',
276
254
  {
@@ -325,6 +303,7 @@ export const prepareVaultManagerKit = (
325
303
  collateral: {
326
304
  makeVaultInvitation() {
327
305
  const { facets } = this;
306
+ const { collateralBrand, debtBrand } = this.state;
328
307
  return zcf.makeInvitation(
329
308
  seat => this.facets.self.makeVaultKit(seat),
330
309
  facets.manager.scopeDescription('MakeVault'),
@@ -341,36 +320,108 @@ export const prepareVaultManagerKit = (
341
320
  },
342
321
  /** @deprecated use getPublicTopics */
343
322
  getSubscriber() {
344
- return topics.asset.subscriber;
323
+ return this.state.assetTopicKit.subscriber;
345
324
  },
346
325
  /** @deprecated use getPublicTopics */
347
326
  getMetrics() {
348
- return topics.metrics.subscriber;
327
+ return this.state.metricsTopicKit.subscriber;
349
328
  },
350
329
  getQuotes() {
351
- return storedQuotesNotifier;
330
+ const ephemera = collateralEphemera(this.state.collateralBrand);
331
+ return ephemera.storedQuotesNotifier;
352
332
  },
353
333
  getCompoundedInterest() {
354
334
  return this.state.compoundedInterest;
355
335
  },
356
336
  getPublicTopics() {
357
- return topics;
337
+ const { assetTopicKit, metricsTopicKit } = this.state;
338
+ return harden({
339
+ asset: makeRecorderTopic(
340
+ 'State of the assets managed',
341
+ assetTopicKit,
342
+ ),
343
+ metrics: makeRecorderTopic(
344
+ 'Vault Factory metrics',
345
+ metricsTopicKit,
346
+ ),
347
+ });
358
348
  },
359
349
  },
360
350
 
361
351
  // Some of these could go in closures but are kept on a facet anticipating future durability options.
362
352
  helper: {
353
+ /**
354
+ * Start non-durable processes (or restart if needed after vat restart)
355
+ */
356
+ start() {
357
+ const { state, facets } = this;
358
+ const {
359
+ collateralBrand,
360
+ collateralUnit,
361
+ debtBrand,
362
+ storageNode,
363
+ unsettledVaults,
364
+ } = state;
365
+
366
+ const ephemera = collateralEphemera(collateralBrand);
367
+ ephemera.prioritizedVaults = makePrioritizedVaults(unsettledVaults);
368
+
369
+ const periodNotifier = E(timerService).makeNotifier(
370
+ 0n,
371
+ factoryPowers
372
+ .getGovernedParams(collateralBrand)
373
+ .getChargingPeriod(),
374
+ );
375
+
376
+ void observeNotifier(periodNotifier, {
377
+ updateState: updateTime =>
378
+ facets.helper
379
+ .chargeAllVaults(updateTime)
380
+ .catch(e =>
381
+ console.error('🚨 vaultManager failed to charge interest', e),
382
+ ),
383
+ fail: reason => {
384
+ zcf.shutdownWithFailure(
385
+ assert.error(X`Unable to continue without a timer: ${reason}`),
386
+ );
387
+ },
388
+ finish: done => {
389
+ zcf.shutdownWithFailure(
390
+ assert.error(X`Unable to continue without a timer: ${done}`),
391
+ );
392
+ },
393
+ });
394
+
395
+ const quoteNotifier = E(priceAuthority).makeQuoteNotifier(
396
+ collateralUnit,
397
+ debtBrand,
398
+ );
399
+ ephemera.storedQuotesNotifier = makeStoredNotifier(
400
+ quoteNotifier,
401
+ E(storageNode).makeChildNode('quotes'),
402
+ marshaller,
403
+ );
404
+ void observeNotifier(quoteNotifier, {
405
+ updateState(value) {
406
+ ephemera.storedCollateralQuote = value;
407
+ },
408
+ fail(reason) {
409
+ console.error('quoteNotifier failed to iterate', reason);
410
+ },
411
+ });
412
+ },
363
413
  /**
364
414
  * @param {Timestamp} updateTime
365
415
  */
366
416
  async chargeAllVaults(updateTime) {
367
417
  const { state, facets } = this;
418
+ const { collateralBrand, debtMint, poolIncrementSeat } = state;
368
419
  trace('chargeAllVaults', collateralBrand, {
369
420
  updateTime,
370
421
  });
371
422
 
372
423
  const interestRate = factoryPowers
373
- .getGovernedParams()
424
+ .getGovernedParams(collateralBrand)
374
425
  .getInterestRate();
375
426
 
376
427
  // Update state with the results of charging interest
@@ -385,10 +436,10 @@ export const prepareVaultManagerKit = (
385
436
  {
386
437
  interestRate,
387
438
  chargingPeriod: factoryPowers
388
- .getGovernedParams()
439
+ .getGovernedParams(collateralBrand)
389
440
  .getChargingPeriod(),
390
441
  recordingPeriod: factoryPowers
391
- .getGovernedParams()
442
+ .getGovernedParams(collateralBrand)
392
443
  .getRecordingPeriod(),
393
444
  },
394
445
  {
@@ -405,11 +456,11 @@ export const prepareVaultManagerKit = (
405
456
 
406
457
  return facets.helper.assetNotify();
407
458
  },
408
-
409
459
  assetNotify() {
410
460
  const { state } = this;
461
+ const { collateralBrand, assetTopicKit } = state;
411
462
  const interestRate = factoryPowers
412
- .getGovernedParams()
463
+ .getGovernedParams(collateralBrand)
413
464
  .getInterestRate();
414
465
  /** @type {AssetState} */
415
466
  const payload = harden({
@@ -417,7 +468,7 @@ export const prepareVaultManagerKit = (
417
468
  interestRate,
418
469
  latestInterestUpdate: state.latestInterestUpdate,
419
470
  });
420
- return assetKit.recorder.write(payload);
471
+ return assetTopicKit.recorder.write(payload);
421
472
  },
422
473
  burnToCoverDebt(debt, proceeds, seat) {
423
474
  const { state } = this;
@@ -526,6 +577,9 @@ export const prepareVaultManagerKit = (
526
577
  },
527
578
  updateMetrics() {
528
579
  const { state } = this;
580
+ const { collateralBrand, retainedCollateralSeat, metricsTopicKit } =
581
+ state;
582
+ const { prioritizedVaults } = collateralEphemera(collateralBrand);
529
583
 
530
584
  const retainedCollateral =
531
585
  retainedCollateralSeat.getCurrentAllocation()?.Collateral ??
@@ -534,7 +588,7 @@ export const prepareVaultManagerKit = (
534
588
  /** @type {MetricsNotification} */
535
589
  const payload = harden({
536
590
  numActiveVaults: prioritizedVaults.getCount(),
537
- numLiquidatingVaults: liquidatingVaults.getSize(),
591
+ numLiquidatingVaults: state.liquidatingVaults.getSize(),
538
592
  totalCollateral: state.totalCollateral,
539
593
  totalDebt: state.totalDebt,
540
594
  retainedCollateral,
@@ -549,7 +603,7 @@ export const prepareVaultManagerKit = (
549
603
  totalShortfallReceived: state.totalShortfallReceived,
550
604
  });
551
605
 
552
- return E(metricsTopicKit.recorderP).write(payload);
606
+ return E(metricsTopicKit.recorder).write(payload);
553
607
  },
554
608
 
555
609
  distributeProceeds(
@@ -561,6 +615,7 @@ export const prepareVaultManagerKit = (
561
615
  totalCollateral,
562
616
  ) {
563
617
  const { state, facets } = this;
618
+ const { collateralBrand, debtBrand, liquidatingVaults } = this.state;
564
619
 
565
620
  state.totalCollateralSold = AmountMath.add(
566
621
  state.totalCollateralSold,
@@ -792,6 +847,7 @@ export const prepareVaultManagerKit = (
792
847
  if (transfers.length > 0) {
793
848
  atomicRearrange(zcf, harden(transfers));
794
849
  }
850
+ const { prioritizedVaults } = collateralEphemera(collateralBrand);
795
851
  for (const [vaultId, vault] of vaultsToReinstate.entries()) {
796
852
  prioritizedVaults.addVault(vaultId, vault);
797
853
  }
@@ -815,7 +871,8 @@ export const prepareVaultManagerKit = (
815
871
 
816
872
  manager: {
817
873
  getGovernedParams() {
818
- return factoryPowers.getGovernedParams();
874
+ const { collateralBrand } = this.state;
875
+ return factoryPowers.getGovernedParams(collateralBrand);
819
876
  },
820
877
 
821
878
  /**
@@ -825,6 +882,8 @@ export const prepareVaultManagerKit = (
825
882
  * @param {Amount<'nat'>} collateralAmount
826
883
  */
827
884
  maxDebtFor(collateralAmount) {
885
+ const { collateralBrand } = this.state;
886
+ const { storedCollateralQuote } = collateralEphemera(collateralBrand);
828
887
  if (!storedCollateralQuote)
829
888
  throw Fail`maxDebtFor called before a collateral quote was available`;
830
889
  // use the lower price to prevent vault adjustments that put them imminently underwater
@@ -834,8 +893,12 @@ export const prepareVaultManagerKit = (
834
893
  );
835
894
  const collatlVal = ceilMultiplyBy(collateralAmount, collateralPrice);
836
895
  const minimumCollateralization = calculateMinimumCollateralization(
837
- factoryPowers.getGovernedParams().getLiquidationMargin(),
838
- factoryPowers.getGovernedParams().getLiquidationPadding(),
896
+ factoryPowers
897
+ .getGovernedParams(collateralBrand)
898
+ .getLiquidationMargin(),
899
+ factoryPowers
900
+ .getGovernedParams(collateralBrand)
901
+ .getLiquidationPadding(),
839
902
  );
840
903
  // floorDivide because we want the debt ceiling lower
841
904
  return floorDivideBy(collatlVal, minimumCollateralization);
@@ -843,10 +906,10 @@ export const prepareVaultManagerKit = (
843
906
  /** @type {MintAndTransfer} */
844
907
  mintAndTransfer(mintReceiver, toMint, fee, transfers) {
845
908
  const { state } = this;
846
- const { totalDebt } = state;
909
+ const { collateralBrand, totalDebt } = state;
847
910
 
848
911
  checkDebtLimit(
849
- factoryPowers.getGovernedParams().getDebtLimit(),
912
+ factoryPowers.getGovernedParams(collateralBrand).getDebtLimit(),
850
913
  totalDebt,
851
914
  toMint,
852
915
  );
@@ -859,6 +922,7 @@ export const prepareVaultManagerKit = (
859
922
  */
860
923
  burnAndRecord(toBurn, seat) {
861
924
  const { state } = this;
925
+ const { collateralBrand } = this.state;
862
926
 
863
927
  trace('burnAndRecord', collateralBrand, {
864
928
  toBurn,
@@ -868,12 +932,14 @@ export const prepareVaultManagerKit = (
868
932
  state.totalDebt = AmountMath.subtract(state.totalDebt, toBurn);
869
933
  },
870
934
  getAssetSubscriber() {
871
- return assetKit.subscriber;
935
+ return this.state.assetTopicKit.subscriber;
872
936
  },
873
937
  getCollateralBrand() {
938
+ const { collateralBrand } = this.state;
874
939
  return collateralBrand;
875
940
  },
876
941
  getDebtBrand() {
942
+ const { debtBrand } = this.state;
877
943
  return debtBrand;
878
944
  },
879
945
  /**
@@ -882,6 +948,7 @@ export const prepareVaultManagerKit = (
882
948
  * @param {string} base
883
949
  */
884
950
  scopeDescription(base) {
951
+ const { descriptionScope } = this.state;
885
952
  return `${descriptionScope}: ${base}`;
886
953
  },
887
954
  /**
@@ -915,6 +982,18 @@ export const prepareVaultManagerKit = (
915
982
  AmountMath.isEmpty(oldDebtNormalized) &&
916
983
  vaultPhase !== Phase.ACTIVE;
917
984
 
985
+ trace('handleBalanceChange', {
986
+ oldDebtNormalized,
987
+ oldCollateral,
988
+ vaultId,
989
+ vaultPhase,
990
+ vault,
991
+ settled,
992
+ });
993
+
994
+ const { prioritizedVaults } = collateralEphemera(
995
+ state.collateralBrand,
996
+ );
918
997
  if (settled) {
919
998
  assert(
920
999
  !prioritizedVaults.hasVaultByAttributes(
@@ -926,6 +1005,7 @@ export const prepareVaultManagerKit = (
926
1005
  );
927
1006
  } else {
928
1007
  const isNew = AmountMath.isEmpty(oldDebtNormalized);
1008
+ trace({ isNew });
929
1009
  if (!isNew) {
930
1010
  // its position in the queue is no longer valid
931
1011
 
@@ -938,6 +1018,7 @@ export const prepareVaultManagerKit = (
938
1018
  vault === vaultInStore,
939
1019
  'handleBalanceChange for two different vaults',
940
1020
  );
1021
+ trace('removed', vault, vaultId);
941
1022
  }
942
1023
 
943
1024
  // replace in queue, but only if it can accrue interest or be liquidated (i.e. has debt).
@@ -961,18 +1042,20 @@ export const prepareVaultManagerKit = (
961
1042
  },
962
1043
  self: {
963
1044
  getGovernedParams() {
964
- return factoryPowers.getGovernedParams();
1045
+ const { collateralBrand } = this.state;
1046
+ return factoryPowers.getGovernedParams(collateralBrand);
965
1047
  },
966
1048
 
967
1049
  /**
968
1050
  * @param {ZCFSeat} seat
969
1051
  */
970
1052
  async makeVaultKit(seat) {
971
- trace('makevaultKit');
1053
+ trace('makeVaultKit');
972
1054
  const {
973
1055
  state,
974
1056
  facets: { manager },
975
1057
  } = this;
1058
+ const { storageNode } = this.state;
976
1059
  assert(marshaller, 'makeVaultKit missing marshaller');
977
1060
  assert(storageNode, 'makeVaultKit missing storageNode');
978
1061
  assert(zcf, 'makeVaultKit missing zcf');
@@ -985,7 +1068,7 @@ export const prepareVaultManagerKit = (
985
1068
  ).makeChildNode(`vault${vaultId}`);
986
1069
 
987
1070
  const { self: vault } = makeVault(manager, vaultId, vaultStorageNode);
988
- trace('makevaultKit made vault', vault);
1071
+ trace('makeVaultKit made vault', vault);
989
1072
 
990
1073
  try {
991
1074
  // TODO `await` is allowed until the above ordering is fixed
@@ -1011,8 +1094,11 @@ export const prepareVaultManagerKit = (
1011
1094
  // remove it from the store if it got in
1012
1095
  /** @type {NormalizedDebt} */
1013
1096
  // @ts-expect-error cast
1014
- const normalizedDebt = AmountMath.makeEmpty(debtBrand);
1097
+ const normalizedDebt = AmountMath.makeEmpty(state.debtBrand);
1015
1098
  const collateralPre = seat.getCurrentAllocation().Collateral;
1099
+ const { prioritizedVaults } = collateralEphemera(
1100
+ state.collateralBrand,
1101
+ );
1016
1102
  try {
1017
1103
  prioritizedVaults.removeVaultByAttributes(
1018
1104
  normalizedDebt,
@@ -1040,6 +1126,9 @@ export const prepareVaultManagerKit = (
1040
1126
  },
1041
1127
 
1042
1128
  getCollateralQuote() {
1129
+ const { storedCollateralQuote } = collateralEphemera(
1130
+ this.state.collateralBrand,
1131
+ );
1043
1132
  if (!storedCollateralQuote)
1044
1133
  throw Fail`getCollateralQuote called before a collateral quote was available`;
1045
1134
  return storedCollateralQuote;
@@ -1050,9 +1139,12 @@ export const prepareVaultManagerKit = (
1050
1139
  },
1051
1140
 
1052
1141
  lockOraclePrices() {
1142
+ const { state } = this;
1143
+ const { storedCollateralQuote } = collateralEphemera(
1144
+ state.collateralBrand,
1145
+ );
1053
1146
  if (!storedCollateralQuote)
1054
1147
  throw Fail`lockOraclePrices called before a collateral quote was available`;
1055
- const { state } = this;
1056
1148
  trace(
1057
1149
  `lockPrice`,
1058
1150
  getAmountIn(storedCollateralQuote),
@@ -1068,9 +1160,16 @@ export const prepareVaultManagerKit = (
1068
1160
  async liquidateVaults(auctionPF) {
1069
1161
  const { state, facets } = this;
1070
1162
  const { self, helper } = facets;
1071
- const { lockedQuote, compoundedInterest } = state;
1163
+ const {
1164
+ collateralBrand,
1165
+ compoundedInterest,
1166
+ debtBrand,
1167
+ liquidatingVaults,
1168
+ lockedQuote,
1169
+ } = state;
1072
1170
  trace('considering liquidation');
1073
1171
 
1172
+ const { prioritizedVaults } = collateralEphemera(collateralBrand);
1074
1173
  assert(factoryPowers && prioritizedVaults && zcf);
1075
1174
  lockedQuote ||
1076
1175
  Fail`Must have locked a quote before liquidating vaults.`;
@@ -1122,6 +1221,10 @@ export const prepareVaultManagerKit = (
1122
1221
  // and the difference between startingRate and lowestRate.
1123
1222
  const [proceeds] = await Promise.all([deposited, userSeatPromise]);
1124
1223
 
1224
+ const { storedCollateralQuote } = collateralEphemera(
1225
+ this.state.collateralBrand,
1226
+ );
1227
+
1125
1228
  trace(`LiqV after long wait`, proceeds);
1126
1229
  helper.distributeProceeds(
1127
1230
  proceeds,
@@ -1137,45 +1240,74 @@ export const prepareVaultManagerKit = (
1137
1240
 
1138
1241
  {
1139
1242
  finish: ({ state, facets: { helper } }) => {
1140
- void assetKit.recorder.write(
1243
+ helper.start();
1244
+ void state.assetTopicKit.recorder.write(
1141
1245
  harden({
1142
1246
  compoundedInterest: state.compoundedInterest,
1143
- interestRate: factoryPowers.getGovernedParams().getInterestRate(),
1247
+ interestRate: factoryPowers
1248
+ .getGovernedParams(state.collateralBrand)
1249
+ .getInterestRate(),
1144
1250
  latestInterestUpdate: state.latestInterestUpdate,
1145
1251
  }),
1146
1252
  );
1147
1253
 
1148
1254
  // push initial state of metrics
1149
1255
  helper.updateMetrics();
1150
-
1151
- void observeNotifier(periodNotifier, {
1152
- updateState: updateTime =>
1153
- helper
1154
- .chargeAllVaults(updateTime)
1155
- .catch(e =>
1156
- console.error('🚨 vaultManager failed to charge interest', e),
1157
- ),
1158
- fail: reason => {
1159
- zcf.shutdownWithFailure(
1160
- assert.error(X`Unable to continue without a timer: ${reason}`),
1161
- );
1162
- },
1163
- finish: done => {
1164
- zcf.shutdownWithFailure(
1165
- assert.error(X`Unable to continue without a timer: ${done}`),
1166
- );
1167
- },
1168
- });
1169
1256
  },
1170
1257
  },
1171
1258
  );
1259
+
1260
+ /** @param {Omit<Parameters<typeof makeVaultManagerKitInternal>[0], 'metricsStorageNode'>} externalParams */
1261
+ const makeVaultManagerKit = async externalParams => {
1262
+ const metricsStorageNode = await E(
1263
+ externalParams.storageNode,
1264
+ ).makeChildNode('metrics');
1265
+ return makeVaultManagerKitInternal({
1266
+ ...externalParams,
1267
+ metricsStorageNode,
1268
+ });
1269
+ };
1270
+ return makeVaultManagerKit;
1172
1271
  };
1173
1272
 
1174
1273
  /**
1175
- * @typedef {ReturnType<ReturnType<typeof prepareVaultManagerKit>>['self']} VaultManager
1274
+ * @typedef {Awaited<ReturnType<ReturnType<typeof prepareVaultManagerKit>>>} VaultManagerKit
1275
+ */
1276
+ /**
1277
+ * @typedef {VaultManagerKit['self']} VaultManager
1176
1278
  * Each VaultManager manages a single collateral type.
1177
1279
  *
1178
1280
  * It manages some number of outstanding debt positions, each called a Vault,
1179
1281
  * for which the collateral is provided in exchange for borrowed Minted.
1180
1282
  */
1181
- /** @typedef {ReturnType<VaultManager['getPublicFacet']>} CollateralManager */
1283
+ /** @typedef {VaultManagerKit['collateral']} CollateralManager */
1284
+
1285
+ /**
1286
+ * Support restarting kits from baggage and mutating the array holding them
1287
+ *
1288
+ * @param {import('@agoric/vat-data').Baggage} baggage
1289
+ */
1290
+ export const provideAndStartVaultManagerKits = baggage => {
1291
+ const key = 'vaultManagerKits';
1292
+
1293
+ const noKits = /** @type {VaultManagerKit[]} */ (harden([]));
1294
+
1295
+ for (const kit of provide(baggage, key, () => noKits)) {
1296
+ kit.helper.start();
1297
+ }
1298
+
1299
+ return {
1300
+ /** @type {(kit: VaultManagerKit) => void} */
1301
+ add: kit => {
1302
+ appendToStoredArray(baggage, key, kit);
1303
+ },
1304
+ /** @type {(index: number) => VaultManagerKit} */
1305
+ get: index => {
1306
+ const kits = baggage.get(key);
1307
+ index < kits.length || Fail`no VaultManagerKit at index ${index}`;
1308
+ return kits[index];
1309
+ },
1310
+ length: () => baggage.get(key).length,
1311
+ };
1312
+ };
1313
+ harden(provideAndStartVaultManagerKits);