@agoric/inter-protocol 0.13.2-dev-876c738.0 → 0.13.2-dev-8ed2bf6.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.
- package/bundles/bundle-amm-meta.js +125 -125
- package/bundles/bundle-amm.js +1 -1
- package/bundles/bundle-econCommitteeCharter-meta.js +70 -70
- package/bundles/bundle-econCommitteeCharter.js +1 -1
- package/bundles/bundle-feeDistributor-meta.js +53 -53
- package/bundles/bundle-feeDistributor.js +1 -1
- package/bundles/bundle-interchainPool-meta.js +103 -103
- package/bundles/bundle-interchainPool.js +1 -1
- package/bundles/bundle-liquidateIncrementally-meta.js +68 -68
- package/bundles/bundle-liquidateIncrementally.js +1 -1
- package/bundles/bundle-liquidateMinimum-meta.js +68 -68
- package/bundles/bundle-liquidateMinimum.js +1 -1
- package/bundles/bundle-priceAggregatorChainlink-meta.js +72 -72
- package/bundles/bundle-priceAggregatorChainlink.js +1 -1
- package/bundles/bundle-psm-meta.js +106 -106
- package/bundles/bundle-psm.js +1 -1
- package/bundles/bundle-reserve-meta.js +180 -180
- package/bundles/bundle-reserve.js +1 -1
- package/bundles/bundle-scaledPriceAuthority-meta.js +68 -68
- package/bundles/bundle-scaledPriceAuthority.js +1 -1
- package/bundles/bundle-stakeFactory-meta.js +114 -114
- package/bundles/bundle-stakeFactory.js +1 -1
- package/bundles/bundle-vaultFactory-meta.js +123 -123
- package/bundles/bundle-vaultFactory.js +1 -1
- package/package.json +15 -15
- package/src/contractSupport.js +42 -1
- package/src/vaultFactory/vault.js +619 -605
- package/src/vaultFactory/vaultDirector.js +42 -15
- package/src/vaultFactory/vaultHolder.js +103 -93
- package/src/vaultFactory/vaultKit.js +31 -32
- package/src/vaultFactory/vaultManager.js +853 -829
|
@@ -16,41 +16,36 @@ import '@agoric/zoe/exported.js';
|
|
|
16
16
|
|
|
17
17
|
import { AmountMath, AmountShape, BrandShape, RatioShape } from '@agoric/ertp';
|
|
18
18
|
import {
|
|
19
|
-
|
|
19
|
+
makeStoredSubscriber,
|
|
20
20
|
observeNotifier,
|
|
21
21
|
SubscriberShape,
|
|
22
|
+
vivifyDurablePublishKit,
|
|
22
23
|
} from '@agoric/notifier';
|
|
23
24
|
import {
|
|
24
|
-
defineDurableFarClassKit,
|
|
25
25
|
M,
|
|
26
|
-
makeKindHandle,
|
|
27
26
|
makeScalarBigMapStore,
|
|
28
27
|
makeScalarBigSetStore,
|
|
29
|
-
|
|
28
|
+
vivifyFarClassKit,
|
|
30
29
|
} from '@agoric/vat-data';
|
|
31
30
|
import {
|
|
32
31
|
assertProposalShape,
|
|
32
|
+
atomicTransfer,
|
|
33
33
|
ceilDivideBy,
|
|
34
34
|
floorDivideBy,
|
|
35
35
|
getAmountIn,
|
|
36
36
|
getAmountOut,
|
|
37
37
|
makeRatio,
|
|
38
38
|
makeRatioFromAmounts,
|
|
39
|
-
atomicTransfer,
|
|
40
39
|
} from '@agoric/zoe/src/contractSupport/index.js';
|
|
41
|
-
import { E } from '@endo/eventual-send';
|
|
42
40
|
import { unitAmount } from '@agoric/zoe/src/contractSupport/priceQuote.js';
|
|
43
41
|
import { InstallationShape, SeatShape } from '@agoric/zoe/src/typeGuards.js';
|
|
44
|
-
import {
|
|
45
|
-
|
|
46
|
-
makeEphemeraProvider,
|
|
47
|
-
makeMetricsPublisherKit,
|
|
48
|
-
} from '../contractSupport.js';
|
|
42
|
+
import { E } from '@endo/eventual-send';
|
|
43
|
+
import { checkDebtLimit, makeEphemeraProvider } from '../contractSupport.js';
|
|
49
44
|
import { chargeInterest } from '../interest.js';
|
|
50
45
|
import { makeTracer } from '../makeTracer.js';
|
|
51
46
|
import { liquidate, makeQuote, updateQuote } from './liquidation.js';
|
|
52
47
|
import { makePrioritizedVaults } from './prioritizedVaults.js';
|
|
53
|
-
import {
|
|
48
|
+
import { Phase, vivifyVault } from './vault.js';
|
|
54
49
|
|
|
55
50
|
const { details: X } = assert;
|
|
56
51
|
|
|
@@ -97,9 +92,13 @@ const trace = makeTracer('VM', false);
|
|
|
97
92
|
|
|
98
93
|
/**
|
|
99
94
|
* @typedef {Readonly<{
|
|
95
|
+
* assetSubscriber: Subscriber<AssetState>,
|
|
96
|
+
* assetPublisher: Publisher<AssetState>,
|
|
100
97
|
* collateralBrand: Brand<'nat'>,
|
|
101
98
|
* debtBrand: Brand<'nat'>,
|
|
102
99
|
* debtMint: ZCFMint<'nat'>,
|
|
100
|
+
* metricsPublisher: Publisher<MetricsNotification>,
|
|
101
|
+
* metricsSubscriber: Subscriber<MetricsNotification>,
|
|
103
102
|
* poolIncrementSeat: ZCFSeat,
|
|
104
103
|
* retainedCollateralSeat: ZCFSeat,
|
|
105
104
|
* unsettledVaults: MapStore<string, Vault>,
|
|
@@ -135,8 +134,6 @@ const trace = makeTracer('VM', false);
|
|
|
135
134
|
* using a proxy for the manager object, collateralBrand.
|
|
136
135
|
*
|
|
137
136
|
* @type {(collateralBrand: Brand) => Partial<{
|
|
138
|
-
* assetSubscriber: Subscriber<AssetState>,
|
|
139
|
-
* assetPublisher: Publisher<AssetState>,
|
|
140
137
|
* factoryPowers: import('./vaultDirector.js').FactoryPowersFacet?,
|
|
141
138
|
* liquidationQueueing: boolean,
|
|
142
139
|
* outstandingQuote: Promise<MutableQuote>?,
|
|
@@ -144,8 +141,8 @@ const trace = makeTracer('VM', false);
|
|
|
144
141
|
* periodNotifier: ERef<Notifier<Timestamp>>,
|
|
145
142
|
* priceAuthority: ERef<PriceAuthority>,
|
|
146
143
|
* prioritizedVaults: ReturnType<typeof makePrioritizedVaults>,
|
|
147
|
-
*
|
|
148
|
-
*
|
|
144
|
+
* storedAssetSubscriber: StoredSubscriber<AssetState>,
|
|
145
|
+
* storedMetricsSubscriber: StoredSubscriber<MetricsNotification>,
|
|
149
146
|
* storageNode: ERef<StorageNode>,
|
|
150
147
|
* zcf: import('./vaultFactory.js').VaultFactoryZCF,
|
|
151
148
|
* }>} */
|
|
@@ -153,127 +150,6 @@ const provideEphemera = makeEphemeraProvider(() => ({
|
|
|
153
150
|
liquidationQueueing: false,
|
|
154
151
|
}));
|
|
155
152
|
|
|
156
|
-
/**
|
|
157
|
-
* Create state for the Vault Manager kind
|
|
158
|
-
*
|
|
159
|
-
* @param {import('./vaultFactory.js').VaultFactoryZCF} zcf
|
|
160
|
-
* @param {ZCFMint<'nat'>} debtMint
|
|
161
|
-
* @param {Brand<'nat'>} collateralBrand
|
|
162
|
-
* @param {ERef<PriceAuthority>} priceAuthority
|
|
163
|
-
* @param {import('./vaultDirector.js').FactoryPowersFacet} factoryPowers
|
|
164
|
-
* @param {ERef<TimerService>} timerService
|
|
165
|
-
* @param {Timestamp} startTimeStamp
|
|
166
|
-
* @param {ERef<StorageNode>} storageNode
|
|
167
|
-
* @param {ERef<Marshaller>} marshaller
|
|
168
|
-
*/
|
|
169
|
-
const initState = (
|
|
170
|
-
zcf,
|
|
171
|
-
debtMint,
|
|
172
|
-
collateralBrand,
|
|
173
|
-
priceAuthority,
|
|
174
|
-
factoryPowers,
|
|
175
|
-
timerService,
|
|
176
|
-
startTimeStamp,
|
|
177
|
-
storageNode,
|
|
178
|
-
marshaller,
|
|
179
|
-
) => {
|
|
180
|
-
assert(
|
|
181
|
-
storageNode && marshaller,
|
|
182
|
-
'VaultManager missing storageNode or marshaller',
|
|
183
|
-
);
|
|
184
|
-
|
|
185
|
-
const periodNotifier = E(timerService).makeNotifier(
|
|
186
|
-
0n,
|
|
187
|
-
factoryPowers.getGovernedParams().getChargingPeriod(),
|
|
188
|
-
);
|
|
189
|
-
|
|
190
|
-
const debtBrand = debtMint.getIssuerRecord().brand;
|
|
191
|
-
const zeroCollateral = AmountMath.makeEmpty(collateralBrand, 'nat');
|
|
192
|
-
const zeroDebt = AmountMath.makeEmpty(debtBrand, 'nat');
|
|
193
|
-
|
|
194
|
-
const { metricsPublication, metricsSubscription } = makeMetricsPublisherKit(
|
|
195
|
-
storageNode,
|
|
196
|
-
marshaller,
|
|
197
|
-
);
|
|
198
|
-
|
|
199
|
-
/** @type {MapStore<string, Vault>} */
|
|
200
|
-
const unsettledVaults = makeScalarBigMapStore('orderedVaultStore', {
|
|
201
|
-
durable: true,
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* If things are going well, the set will contain at most one Vault. Otherwise
|
|
206
|
-
* failures remain and are available to be repaired via contract upgrade.
|
|
207
|
-
*
|
|
208
|
-
* @type {SetStore<Vault>}
|
|
209
|
-
*/
|
|
210
|
-
const liquidatingVaults = makeScalarBigSetStore('liquidatingVaults', {
|
|
211
|
-
durable: true,
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
/** @type {ImmutableState} */
|
|
215
|
-
const fixed = {
|
|
216
|
-
collateralBrand,
|
|
217
|
-
debtBrand,
|
|
218
|
-
debtMint,
|
|
219
|
-
poolIncrementSeat: zcf.makeEmptySeatKit().zcfSeat,
|
|
220
|
-
retainedCollateralSeat: zcf.makeEmptySeatKit().zcfSeat,
|
|
221
|
-
unsettledVaults,
|
|
222
|
-
liquidatingVaults,
|
|
223
|
-
};
|
|
224
|
-
|
|
225
|
-
const compoundedInterest = makeRatio(100n, fixed.debtBrand); // starts at 1.0, no interest
|
|
226
|
-
// timestamp of most recent update to interest
|
|
227
|
-
const latestInterestUpdate = startTimeStamp;
|
|
228
|
-
|
|
229
|
-
/** @type {PublishKit<AssetState>} */
|
|
230
|
-
const { publisher: assetPublisher, subscriber: assetSubscriber } =
|
|
231
|
-
makeStoredPublishKit(storageNode, marshaller);
|
|
232
|
-
|
|
233
|
-
assetPublisher.publish(
|
|
234
|
-
harden({
|
|
235
|
-
compoundedInterest,
|
|
236
|
-
interestRate: factoryPowers.getGovernedParams().getInterestRate(),
|
|
237
|
-
latestInterestUpdate,
|
|
238
|
-
}),
|
|
239
|
-
);
|
|
240
|
-
|
|
241
|
-
const ephemera = provideEphemera(collateralBrand);
|
|
242
|
-
Object.assign(ephemera, {
|
|
243
|
-
assetSubscriber,
|
|
244
|
-
assetPublisher,
|
|
245
|
-
factoryPowers,
|
|
246
|
-
marshaller,
|
|
247
|
-
metricsPublication,
|
|
248
|
-
metricsSubscription,
|
|
249
|
-
periodNotifier,
|
|
250
|
-
priceAuthority,
|
|
251
|
-
prioritizedVaults: makePrioritizedVaults(unsettledVaults),
|
|
252
|
-
storageNode,
|
|
253
|
-
zcf,
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
/** @type {MutableState & ImmutableState} */
|
|
257
|
-
const state = {
|
|
258
|
-
...fixed,
|
|
259
|
-
compoundedInterest,
|
|
260
|
-
debtBrand: fixed.debtBrand,
|
|
261
|
-
latestInterestUpdate,
|
|
262
|
-
liquidator: undefined,
|
|
263
|
-
liquidatorInstance: undefined,
|
|
264
|
-
numLiquidationsCompleted: 0,
|
|
265
|
-
totalCollateral: zeroCollateral,
|
|
266
|
-
totalDebt: zeroDebt,
|
|
267
|
-
totalOverageReceived: zeroDebt,
|
|
268
|
-
totalProceedsReceived: zeroDebt,
|
|
269
|
-
totalCollateralSold: zeroCollateral,
|
|
270
|
-
totalShortfallReceived: zeroDebt,
|
|
271
|
-
vaultCounter: 0,
|
|
272
|
-
};
|
|
273
|
-
|
|
274
|
-
return state;
|
|
275
|
-
};
|
|
276
|
-
|
|
277
153
|
// XXX type error when destructuring params
|
|
278
154
|
const finish = context => {
|
|
279
155
|
const {
|
|
@@ -310,738 +186,886 @@ const finish = context => {
|
|
|
310
186
|
});
|
|
311
187
|
};
|
|
312
188
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
189
|
+
// TODO move params of initState here because it's a singleton
|
|
190
|
+
// and remove from State what doesn't need to be stored between upgrades
|
|
191
|
+
export const vivifyVaultManagerKit = baggage => {
|
|
192
|
+
const makeVault = vivifyVault(baggage);
|
|
193
|
+
const makeVaultManagerMetricsPublishKit = vivifyDurablePublishKit(
|
|
194
|
+
baggage,
|
|
195
|
+
'Vault Manager metrics',
|
|
196
|
+
);
|
|
197
|
+
const makeVaultManagerAssetsPublishKit = vivifyDurablePublishKit(
|
|
198
|
+
baggage,
|
|
199
|
+
'Vault Manager assets',
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Create state for the Vault Manager kind
|
|
204
|
+
*
|
|
205
|
+
* @param {import('./vaultFactory.js').VaultFactoryZCF} zcf
|
|
206
|
+
* @param {ZCFMint<'nat'>} debtMint
|
|
207
|
+
* @param {Brand<'nat'>} collateralBrand
|
|
208
|
+
* @param {ERef<PriceAuthority>} priceAuthority
|
|
209
|
+
* @param {import('./vaultDirector.js').FactoryPowersFacet} factoryPowers
|
|
210
|
+
* @param {ERef<TimerService>} timerService
|
|
211
|
+
* @param {Timestamp} startTimeStamp
|
|
212
|
+
* @param {ERef<StorageNode>} storageNode
|
|
213
|
+
* @param {ERef<Marshaller>} marshaller
|
|
214
|
+
*/
|
|
215
|
+
const initState = (
|
|
216
|
+
zcf,
|
|
217
|
+
debtMint,
|
|
218
|
+
collateralBrand,
|
|
219
|
+
priceAuthority,
|
|
220
|
+
factoryPowers,
|
|
221
|
+
timerService,
|
|
222
|
+
startTimeStamp,
|
|
223
|
+
storageNode,
|
|
224
|
+
marshaller,
|
|
225
|
+
) => {
|
|
226
|
+
assert(
|
|
227
|
+
storageNode && marshaller,
|
|
228
|
+
'VaultManager missing storageNode or marshaller',
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
const periodNotifier = E(timerService).makeNotifier(
|
|
232
|
+
0n,
|
|
233
|
+
factoryPowers.getGovernedParams().getChargingPeriod(),
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
const debtBrand = debtMint.getIssuerRecord().brand;
|
|
237
|
+
const zeroCollateral = AmountMath.makeEmpty(collateralBrand, 'nat');
|
|
238
|
+
const zeroDebt = AmountMath.makeEmpty(debtBrand, 'nat');
|
|
239
|
+
|
|
240
|
+
const { publisher: metricsPublisher, subscriber: metricsSubscriber } =
|
|
241
|
+
makeVaultManagerMetricsPublishKit();
|
|
242
|
+
|
|
243
|
+
/** @type {PublishKit<AssetState>} */
|
|
244
|
+
const { publisher: assetPublisher, subscriber: assetSubscriber } =
|
|
245
|
+
makeVaultManagerAssetsPublishKit();
|
|
246
|
+
|
|
247
|
+
/** @type {MapStore<string, Vault>} */
|
|
248
|
+
const unsettledVaults = makeScalarBigMapStore('orderedVaultStore', {
|
|
249
|
+
durable: true,
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* If things are going well, the set will contain at most one Vault. Otherwise
|
|
254
|
+
* failures remain and are available to be repaired via contract upgrade.
|
|
255
|
+
*
|
|
256
|
+
* @type {SetStore<Vault>}
|
|
257
|
+
*/
|
|
258
|
+
const liquidatingVaults = makeScalarBigSetStore('liquidatingVaults', {
|
|
259
|
+
durable: true,
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
/** @type {ImmutableState} */
|
|
263
|
+
const fixed = {
|
|
264
|
+
collateralBrand,
|
|
265
|
+
debtBrand,
|
|
266
|
+
debtMint,
|
|
267
|
+
poolIncrementSeat: zcf.makeEmptySeatKit().zcfSeat,
|
|
268
|
+
retainedCollateralSeat: zcf.makeEmptySeatKit().zcfSeat,
|
|
269
|
+
unsettledVaults,
|
|
270
|
+
liquidatingVaults,
|
|
271
|
+
assetSubscriber,
|
|
272
|
+
assetPublisher,
|
|
273
|
+
metricsPublisher,
|
|
274
|
+
metricsSubscriber,
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
const compoundedInterest = makeRatio(100n, fixed.debtBrand); // starts at 1.0, no interest
|
|
278
|
+
// timestamp of most recent update to interest
|
|
279
|
+
const latestInterestUpdate = startTimeStamp;
|
|
280
|
+
|
|
281
|
+
assetPublisher.publish(
|
|
282
|
+
harden({
|
|
283
|
+
compoundedInterest,
|
|
284
|
+
interestRate: factoryPowers.getGovernedParams().getInterestRate(),
|
|
285
|
+
latestInterestUpdate,
|
|
286
|
+
}),
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
const storedMetricsSubscriber = makeStoredSubscriber(
|
|
290
|
+
metricsSubscriber,
|
|
291
|
+
E(storageNode).makeChildNode('metrics'),
|
|
292
|
+
marshaller,
|
|
293
|
+
);
|
|
294
|
+
|
|
295
|
+
const storedAssetSubscriber = makeStoredSubscriber(
|
|
296
|
+
assetSubscriber,
|
|
297
|
+
storageNode,
|
|
298
|
+
marshaller,
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
const ephemera = provideEphemera(collateralBrand);
|
|
302
|
+
Object.assign(ephemera, {
|
|
303
|
+
factoryPowers,
|
|
304
|
+
marshaller,
|
|
305
|
+
periodNotifier,
|
|
306
|
+
priceAuthority,
|
|
307
|
+
prioritizedVaults: makePrioritizedVaults(unsettledVaults),
|
|
308
|
+
storedAssetSubscriber,
|
|
309
|
+
storedMetricsSubscriber,
|
|
310
|
+
storageNode,
|
|
311
|
+
zcf,
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
/** @type {MutableState & ImmutableState} */
|
|
315
|
+
const state = {
|
|
316
|
+
...fixed,
|
|
317
|
+
compoundedInterest,
|
|
318
|
+
debtBrand: fixed.debtBrand,
|
|
319
|
+
latestInterestUpdate,
|
|
320
|
+
liquidator: undefined,
|
|
321
|
+
liquidatorInstance: undefined,
|
|
322
|
+
numLiquidationsCompleted: 0,
|
|
323
|
+
totalCollateral: zeroCollateral,
|
|
324
|
+
totalDebt: zeroDebt,
|
|
325
|
+
totalOverageReceived: zeroDebt,
|
|
326
|
+
totalProceedsReceived: zeroDebt,
|
|
327
|
+
totalCollateralSold: zeroCollateral,
|
|
328
|
+
totalShortfallReceived: zeroDebt,
|
|
329
|
+
vaultCounter: 0,
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
return state;
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
// TODO find a way to not have to indent a level deeper than defineDurableFarClassKit does
|
|
336
|
+
return vivifyFarClassKit(
|
|
337
|
+
baggage,
|
|
338
|
+
'VaultManagerKit',
|
|
339
|
+
{
|
|
340
|
+
collateral: M.interface('collateral', {
|
|
341
|
+
makeVaultInvitation: M.call().returns(M.promise()),
|
|
342
|
+
getSubscriber: M.call().returns(SubscriberShape),
|
|
343
|
+
getMetrics: M.call().returns(SubscriberShape),
|
|
344
|
+
getCompoundedInterest: M.call().returns(RatioShape),
|
|
345
|
+
}),
|
|
346
|
+
helper: M.interface(
|
|
347
|
+
'helper',
|
|
348
|
+
// not exposed so sloppy okay
|
|
349
|
+
{},
|
|
350
|
+
{ sloppy: true },
|
|
353
351
|
),
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
},
|
|
352
|
+
manager: M.interface('manager', {
|
|
353
|
+
getGovernedParams: M.call().returns(M.remotable()),
|
|
354
|
+
maxDebtFor: M.call(AmountShape).returns(M.promise()),
|
|
355
|
+
mintAndReallocate: M.call(AmountShape, AmountShape, SeatShape)
|
|
356
|
+
.rest()
|
|
357
|
+
.returns(),
|
|
358
|
+
burnAndRecord: M.call(AmountShape, SeatShape).returns(),
|
|
359
|
+
getAssetSubscriber: M.call().returns(SubscriberShape),
|
|
360
|
+
getCollateralBrand: M.call().returns(BrandShape),
|
|
361
|
+
getDebtBrand: M.call().returns(BrandShape),
|
|
362
|
+
getCompoundedInterest: M.call().returns(RatioShape),
|
|
363
|
+
handleBalanceChange: M.call(
|
|
364
|
+
AmountShape,
|
|
365
|
+
AmountShape,
|
|
366
|
+
M.string(),
|
|
367
|
+
M.string(),
|
|
368
|
+
M.remotable(),
|
|
369
|
+
).returns(),
|
|
370
|
+
}),
|
|
371
|
+
self: M.interface('self', {
|
|
372
|
+
getGovernedParams: M.call().returns(M.remotable()),
|
|
373
|
+
liquidateAll: M.call().returns(M.promise()),
|
|
374
|
+
makeVaultKit: M.call(SeatShape).returns(M.promise()),
|
|
375
|
+
setupLiquidator: M.call(InstallationShape, M.record()).returns(
|
|
376
|
+
M.promise(),
|
|
377
|
+
),
|
|
378
|
+
getCollateralQuote: M.call().returns(M.promise()),
|
|
379
|
+
getPublicFacet: M.call().returns(M.remotable()),
|
|
380
|
+
}),
|
|
384
381
|
},
|
|
382
|
+
initState,
|
|
383
|
+
{
|
|
384
|
+
collateral: {
|
|
385
|
+
makeVaultInvitation() {
|
|
386
|
+
const { zcf } = provideEphemera(this.state.collateralBrand);
|
|
387
|
+
assert(zcf);
|
|
388
|
+
return zcf.makeInvitation(
|
|
389
|
+
seat => this.facets.self.makeVaultKit(seat),
|
|
390
|
+
'MakeVault',
|
|
391
|
+
);
|
|
392
|
+
},
|
|
393
|
+
getSubscriber() {
|
|
394
|
+
const { storedAssetSubscriber } = provideEphemera(
|
|
395
|
+
this.state.collateralBrand,
|
|
396
|
+
);
|
|
397
|
+
assert(storedAssetSubscriber);
|
|
398
|
+
return storedAssetSubscriber;
|
|
399
|
+
},
|
|
400
|
+
getMetrics() {
|
|
401
|
+
const { storedMetricsSubscriber } = provideEphemera(
|
|
402
|
+
this.state.collateralBrand,
|
|
403
|
+
);
|
|
404
|
+
assert(storedMetricsSubscriber);
|
|
405
|
+
return storedMetricsSubscriber;
|
|
406
|
+
},
|
|
407
|
+
getCompoundedInterest() {
|
|
408
|
+
return this.state.compoundedInterest;
|
|
409
|
+
},
|
|
410
|
+
},
|
|
385
411
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
412
|
+
// Some of these could go in closures but are kept on a facet anticipating future durability options.
|
|
413
|
+
helper: {
|
|
414
|
+
/**
|
|
415
|
+
* @param {Timestamp} updateTime
|
|
416
|
+
* @param {ZCFSeat} poolIncrementSeat
|
|
417
|
+
*/
|
|
418
|
+
async chargeAllVaults(updateTime, poolIncrementSeat) {
|
|
419
|
+
const { state, facets } = this;
|
|
420
|
+
trace('chargeAllVaults', state.collateralBrand, {
|
|
421
|
+
updateTime,
|
|
422
|
+
});
|
|
423
|
+
const { factoryPowers } = provideEphemera(state.collateralBrand);
|
|
424
|
+
assert(factoryPowers);
|
|
425
|
+
|
|
426
|
+
const interestRate = factoryPowers
|
|
427
|
+
.getGovernedParams()
|
|
428
|
+
.getInterestRate();
|
|
429
|
+
|
|
430
|
+
// Update state with the results of charging interest
|
|
431
|
+
|
|
432
|
+
const changes = chargeInterest(
|
|
433
|
+
{
|
|
434
|
+
mint: state.debtMint,
|
|
435
|
+
mintAndReallocateWithFee: factoryPowers.mintAndReallocate,
|
|
436
|
+
poolIncrementSeat,
|
|
437
|
+
seatAllocationKeyword: 'Minted',
|
|
438
|
+
},
|
|
439
|
+
{
|
|
440
|
+
interestRate,
|
|
441
|
+
chargingPeriod: factoryPowers
|
|
442
|
+
.getGovernedParams()
|
|
443
|
+
.getChargingPeriod(),
|
|
444
|
+
recordingPeriod: factoryPowers
|
|
445
|
+
.getGovernedParams()
|
|
446
|
+
.getRecordingPeriod(),
|
|
447
|
+
},
|
|
448
|
+
{
|
|
449
|
+
latestInterestUpdate: state.latestInterestUpdate,
|
|
450
|
+
compoundedInterest: state.compoundedInterest,
|
|
451
|
+
totalDebt: state.totalDebt,
|
|
452
|
+
},
|
|
453
|
+
updateTime,
|
|
454
|
+
);
|
|
455
|
+
|
|
456
|
+
state.compoundedInterest = changes.compoundedInterest;
|
|
457
|
+
state.latestInterestUpdate = changes.latestInterestUpdate;
|
|
458
|
+
state.totalDebt = changes.totalDebt;
|
|
459
|
+
|
|
460
|
+
facets.helper.assetNotify();
|
|
461
|
+
trace('chargeAllVaults complete', state.collateralBrand);
|
|
462
|
+
// price to check against has changed
|
|
463
|
+
return facets.helper.reschedulePriceCheck();
|
|
464
|
+
},
|
|
465
|
+
|
|
466
|
+
assetNotify() {
|
|
467
|
+
const { state } = this;
|
|
468
|
+
const ephemera = provideEphemera(state.collateralBrand);
|
|
469
|
+
assert(ephemera.factoryPowers);
|
|
470
|
+
const interestRate = ephemera.factoryPowers
|
|
471
|
+
.getGovernedParams()
|
|
472
|
+
.getInterestRate();
|
|
473
|
+
/** @type {AssetState} */
|
|
474
|
+
const payload = harden({
|
|
475
|
+
compoundedInterest: state.compoundedInterest,
|
|
414
476
|
interestRate,
|
|
415
|
-
chargingPeriod: factoryPowers
|
|
416
|
-
.getGovernedParams()
|
|
417
|
-
.getChargingPeriod(),
|
|
418
|
-
recordingPeriod: factoryPowers
|
|
419
|
-
.getGovernedParams()
|
|
420
|
-
.getRecordingPeriod(),
|
|
421
|
-
},
|
|
422
|
-
{
|
|
423
477
|
latestInterestUpdate: state.latestInterestUpdate,
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
facets.helper.assetNotify();
|
|
435
|
-
trace('chargeAllVaults complete', state.collateralBrand);
|
|
436
|
-
// price to check against has changed
|
|
437
|
-
return facets.helper.reschedulePriceCheck();
|
|
438
|
-
},
|
|
439
|
-
|
|
440
|
-
assetNotify() {
|
|
441
|
-
const { state } = this;
|
|
442
|
-
const ephemera = provideEphemera(state.collateralBrand);
|
|
443
|
-
assert(ephemera.factoryPowers && ephemera.assetPublisher);
|
|
444
|
-
const interestRate = ephemera.factoryPowers
|
|
445
|
-
.getGovernedParams()
|
|
446
|
-
.getInterestRate();
|
|
447
|
-
/** @type {AssetState} */
|
|
448
|
-
const payload = harden({
|
|
449
|
-
compoundedInterest: state.compoundedInterest,
|
|
450
|
-
interestRate,
|
|
451
|
-
latestInterestUpdate: state.latestInterestUpdate,
|
|
452
|
-
// NB: the liquidator is determined by governance but the resulting
|
|
453
|
-
// instance is a concern of the manager. The param manager knows only
|
|
454
|
-
// about the installation and terms of the liqudation contract. We could
|
|
455
|
-
// have another notifier for state downstream of governance changes, but
|
|
456
|
-
// that doesn't seem to be cost-effective.
|
|
457
|
-
liquidatorInstance: state.liquidatorInstance,
|
|
458
|
-
});
|
|
459
|
-
ephemera.assetPublisher.publish(payload);
|
|
460
|
-
},
|
|
478
|
+
// NB: the liquidator is determined by governance but the resulting
|
|
479
|
+
// instance is a concern of the manager. The param manager knows only
|
|
480
|
+
// about the installation and terms of the liqudation contract. We could
|
|
481
|
+
// have another notifier for state downstream of governance changes, but
|
|
482
|
+
// that doesn't seem to be cost-effective.
|
|
483
|
+
liquidatorInstance: state.liquidatorInstance,
|
|
484
|
+
});
|
|
485
|
+
state.assetPublisher.publish(payload);
|
|
486
|
+
},
|
|
461
487
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
);
|
|
467
|
-
assert(metricsPublication && prioritizedVaults);
|
|
468
|
-
|
|
469
|
-
const retainedCollateral =
|
|
470
|
-
state.retainedCollateralSeat.getCurrentAllocation()?.Collateral ??
|
|
471
|
-
AmountMath.makeEmpty(state.collateralBrand, 'nat');
|
|
472
|
-
/** @type {MetricsNotification} */
|
|
473
|
-
const payload = harden({
|
|
474
|
-
numActiveVaults: prioritizedVaults.getCount(),
|
|
475
|
-
numLiquidatingVaults: state.liquidatingVaults.getSize(),
|
|
476
|
-
totalCollateral: state.totalCollateral,
|
|
477
|
-
totalDebt: state.totalDebt,
|
|
478
|
-
retainedCollateral,
|
|
479
|
-
|
|
480
|
-
numLiquidationsCompleted: state.numLiquidationsCompleted,
|
|
481
|
-
totalCollateralSold: state.totalCollateralSold,
|
|
482
|
-
totalOverageReceived: state.totalOverageReceived,
|
|
483
|
-
totalProceedsReceived: state.totalProceedsReceived,
|
|
484
|
-
totalShortfallReceived: state.totalShortfallReceived,
|
|
485
|
-
});
|
|
486
|
-
metricsPublication.updateState(payload);
|
|
487
|
-
},
|
|
488
|
+
updateMetrics() {
|
|
489
|
+
const { state } = this;
|
|
490
|
+
const { prioritizedVaults } = provideEphemera(state.collateralBrand);
|
|
491
|
+
assert(prioritizedVaults);
|
|
488
492
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
* level.
|
|
500
|
-
*
|
|
501
|
-
* @param {Ratio} [highestRatio]
|
|
502
|
-
* @returns {Promise<void>}
|
|
503
|
-
*/
|
|
504
|
-
async reschedulePriceCheck(highestRatio) {
|
|
505
|
-
const { state, facets } = this;
|
|
506
|
-
const { prioritizedVaults, ...ephemera } = provideEphemera(
|
|
507
|
-
state.collateralBrand,
|
|
508
|
-
);
|
|
509
|
-
assert(ephemera.factoryPowers && prioritizedVaults);
|
|
510
|
-
trace('reschedulePriceCheck', state.collateralBrand, ephemera);
|
|
511
|
-
// INTERLOCK: the first time through, start the activity to wait for
|
|
512
|
-
// and process liquidations over time.
|
|
513
|
-
if (!ephemera.liquidationQueueing) {
|
|
514
|
-
ephemera.liquidationQueueing = true;
|
|
515
|
-
// eslint-disable-next-line consistent-return
|
|
516
|
-
return facets.helper
|
|
517
|
-
.processLiquidations()
|
|
518
|
-
.catch(e => console.error('Liquidator failed', e))
|
|
519
|
-
.finally(() => {
|
|
520
|
-
ephemera.liquidationQueueing = false;
|
|
521
|
-
});
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
if (!ephemera.outstandingQuote) {
|
|
525
|
-
// the new threshold will be picked up by the next quote request
|
|
526
|
-
return;
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
const highestDebtRatio =
|
|
530
|
-
highestRatio || prioritizedVaults.highestRatio();
|
|
531
|
-
if (!highestDebtRatio) {
|
|
532
|
-
// if there aren't any open vaults, we don't need an outstanding RFQ.
|
|
533
|
-
trace('no open vaults');
|
|
534
|
-
return;
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
// There is already an activity processing liquidations. It may be
|
|
538
|
-
// waiting for the oracle price to cross a threshold.
|
|
539
|
-
// Update the current in-progress quote.
|
|
540
|
-
const govParams = ephemera.factoryPowers.getGovernedParams();
|
|
541
|
-
const liquidationMargin = govParams.getLiquidationMargin();
|
|
542
|
-
// Safe to call extraneously (lightweight and idempotent)
|
|
543
|
-
updateQuote(
|
|
544
|
-
ephemera.outstandingQuote,
|
|
545
|
-
highestDebtRatio,
|
|
546
|
-
liquidationMargin,
|
|
547
|
-
);
|
|
548
|
-
trace('update quote', state.collateralBrand, highestDebtRatio);
|
|
549
|
-
},
|
|
493
|
+
const retainedCollateral =
|
|
494
|
+
state.retainedCollateralSeat.getCurrentAllocation()?.Collateral ??
|
|
495
|
+
AmountMath.makeEmpty(state.collateralBrand, 'nat');
|
|
496
|
+
/** @type {MetricsNotification} */
|
|
497
|
+
const payload = harden({
|
|
498
|
+
numActiveVaults: prioritizedVaults.getCount(),
|
|
499
|
+
numLiquidatingVaults: state.liquidatingVaults.getSize(),
|
|
500
|
+
totalCollateral: state.totalCollateral,
|
|
501
|
+
totalDebt: state.totalDebt,
|
|
502
|
+
retainedCollateral,
|
|
550
503
|
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
504
|
+
numLiquidationsCompleted: state.numLiquidationsCompleted,
|
|
505
|
+
totalCollateralSold: state.totalCollateralSold,
|
|
506
|
+
totalOverageReceived: state.totalOverageReceived,
|
|
507
|
+
totalProceedsReceived: state.totalProceedsReceived,
|
|
508
|
+
totalShortfallReceived: state.totalShortfallReceived,
|
|
509
|
+
});
|
|
510
|
+
state.metricsPublisher.publish(payload);
|
|
511
|
+
},
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* When any Vault's debt ratio is higher than the current high-water level,
|
|
515
|
+
* call `reschedulePriceCheck()` to request a fresh notification from the
|
|
516
|
+
* priceAuthority. There will be extra outstanding requests since we can't
|
|
517
|
+
* cancel them. (https://github.com/Agoric/agoric-sdk/issues/2713).
|
|
518
|
+
*
|
|
519
|
+
* When the vault with the current highest debt ratio is removed or reduces
|
|
520
|
+
* its ratio, we won't reschedule the priceAuthority requests to reduce churn.
|
|
521
|
+
* Instead, when a priceQuote is received, we'll only reschedule if the
|
|
522
|
+
* high-water level when the request was made matches the current high-water
|
|
523
|
+
* level.
|
|
524
|
+
*
|
|
525
|
+
* @param {Ratio} [highestRatio]
|
|
526
|
+
* @returns {Promise<void>}
|
|
527
|
+
*/
|
|
528
|
+
async reschedulePriceCheck(highestRatio) {
|
|
529
|
+
const { state, facets } = this;
|
|
530
|
+
const { prioritizedVaults, ...ephemera } = provideEphemera(
|
|
531
|
+
state.collateralBrand,
|
|
532
|
+
);
|
|
533
|
+
assert(ephemera.factoryPowers && prioritizedVaults);
|
|
534
|
+
trace('reschedulePriceCheck', state.collateralBrand, ephemera);
|
|
535
|
+
// INTERLOCK: the first time through, start the activity to wait for
|
|
536
|
+
// and process liquidations over time.
|
|
537
|
+
if (!ephemera.liquidationQueueing) {
|
|
538
|
+
ephemera.liquidationQueueing = true;
|
|
539
|
+
// eslint-disable-next-line consistent-return
|
|
540
|
+
return facets.helper
|
|
541
|
+
.processLiquidations()
|
|
542
|
+
.catch(e => console.error('Liquidator failed', e))
|
|
543
|
+
.finally(() => {
|
|
544
|
+
ephemera.liquidationQueueing = false;
|
|
545
|
+
});
|
|
546
|
+
}
|
|
559
547
|
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
if (!highestDebtRatio) {
|
|
565
|
-
return;
|
|
566
|
-
}
|
|
567
|
-
const liquidationMargin = govParams.getLiquidationMargin();
|
|
548
|
+
if (!ephemera.outstandingQuote) {
|
|
549
|
+
// the new threshold will be picked up by the next quote request
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
568
552
|
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
);
|
|
577
|
-
trace(
|
|
578
|
-
'posted quote request',
|
|
579
|
-
state.collateralBrand,
|
|
580
|
-
highestDebtRatio,
|
|
581
|
-
);
|
|
553
|
+
const highestDebtRatio =
|
|
554
|
+
highestRatio || prioritizedVaults.highestRatio();
|
|
555
|
+
if (!highestDebtRatio) {
|
|
556
|
+
// if there aren't any open vaults, we don't need an outstanding RFQ.
|
|
557
|
+
trace('no open vaults');
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
582
560
|
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
trace('quote', state.collateralBrand, quote, quoteRatioPlusMargin);
|
|
561
|
+
// There is already an activity processing liquidations. It may be
|
|
562
|
+
// waiting for the oracle price to cross a threshold.
|
|
563
|
+
// Update the current in-progress quote.
|
|
564
|
+
const govParams = ephemera.factoryPowers.getGovernedParams();
|
|
565
|
+
const liquidationMargin = govParams.getLiquidationMargin();
|
|
566
|
+
// Safe to call extraneously (lightweight and idempotent)
|
|
567
|
+
updateQuote(
|
|
568
|
+
ephemera.outstandingQuote,
|
|
569
|
+
highestDebtRatio,
|
|
570
|
+
liquidationMargin,
|
|
571
|
+
);
|
|
572
|
+
trace('update quote', state.collateralBrand, highestDebtRatio);
|
|
573
|
+
},
|
|
597
574
|
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
575
|
+
async processLiquidations() {
|
|
576
|
+
const { state, facets } = this;
|
|
577
|
+
const { prioritizedVaults, ...ephemera } = provideEphemera(
|
|
578
|
+
state.collateralBrand,
|
|
579
|
+
);
|
|
580
|
+
assert(ephemera.factoryPowers && ephemera.priceAuthority);
|
|
581
|
+
const { priceAuthority } = ephemera;
|
|
582
|
+
const govParams = ephemera.factoryPowers.getGovernedParams();
|
|
583
|
+
|
|
584
|
+
async function* eventualLiquidations() {
|
|
585
|
+
assert(prioritizedVaults);
|
|
586
|
+
while (true) {
|
|
587
|
+
const highestDebtRatio = prioritizedVaults.highestRatio();
|
|
588
|
+
if (!highestDebtRatio) {
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
const liquidationMargin = govParams.getLiquidationMargin();
|
|
592
|
+
|
|
593
|
+
// ask to be alerted when the price level falls enough that the vault
|
|
594
|
+
// with the highest debt to collateral ratio will no longer be valued at the
|
|
595
|
+
// liquidationMargin above its debt.
|
|
596
|
+
ephemera.outstandingQuote = makeQuote(
|
|
597
|
+
priceAuthority,
|
|
598
|
+
highestDebtRatio,
|
|
599
|
+
liquidationMargin,
|
|
600
|
+
);
|
|
601
|
+
trace(
|
|
602
|
+
'posted quote request',
|
|
603
|
+
state.collateralBrand,
|
|
604
|
+
highestDebtRatio,
|
|
605
|
+
);
|
|
606
|
+
|
|
607
|
+
// The rest of this method will not happen until after a quote is received.
|
|
608
|
+
// This may not happen until much later, when the market changes.
|
|
609
|
+
// eslint-disable-next-line no-await-in-loop, @jessie.js/no-nested-await -- loop/nesting to yield each unconditionally
|
|
610
|
+
const quote = await E(ephemera.outstandingQuote).getPromise();
|
|
611
|
+
ephemera.outstandingQuote = null;
|
|
612
|
+
// When we receive a quote, we check whether the vault with the highest
|
|
613
|
+
// ratio of debt to collateral is below the liquidationMargin, and if so,
|
|
614
|
+
// we liquidate it. We use ceilDivide to round up because ratios above
|
|
615
|
+
// this will be liquidated.
|
|
616
|
+
const quoteRatioPlusMargin = makeRatioFromAmounts(
|
|
617
|
+
ceilDivideBy(getAmountOut(quote), liquidationMargin),
|
|
618
|
+
getAmountIn(quote),
|
|
619
|
+
);
|
|
620
|
+
trace(
|
|
621
|
+
'quote',
|
|
622
|
+
state.collateralBrand,
|
|
623
|
+
quote,
|
|
624
|
+
quoteRatioPlusMargin,
|
|
625
|
+
);
|
|
626
|
+
|
|
627
|
+
// Liquidate the head of the queue
|
|
628
|
+
const [next] =
|
|
629
|
+
prioritizedVaults.entriesPrioritizedGTE(quoteRatioPlusMargin);
|
|
630
|
+
if (next) {
|
|
631
|
+
yield next;
|
|
632
|
+
}
|
|
603
633
|
}
|
|
604
634
|
}
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
}
|
|
635
|
+
for await (const next of eventualLiquidations()) {
|
|
636
|
+
await facets.helper.liquidateAndRemove(next);
|
|
637
|
+
trace('price check liq', state.collateralBrand, next && next[0]);
|
|
638
|
+
}
|
|
639
|
+
},
|
|
640
|
+
|
|
641
|
+
/**
|
|
642
|
+
* @param {[key: string, vaultKit: Vault]} record
|
|
643
|
+
*/
|
|
644
|
+
liquidateAndRemove([key, vault]) {
|
|
645
|
+
const { state, facets } = this;
|
|
646
|
+
const { factoryPowers, prioritizedVaults, zcf } = provideEphemera(
|
|
647
|
+
state.collateralBrand,
|
|
648
|
+
);
|
|
649
|
+
assert(factoryPowers && prioritizedVaults && zcf);
|
|
650
|
+
const vaultSeat = vault.getVaultSeat();
|
|
651
|
+
trace('liquidating', state.collateralBrand, vaultSeat.getProposal());
|
|
652
|
+
|
|
653
|
+
const collateralPre = vault.getCollateralAmount();
|
|
654
|
+
|
|
655
|
+
// Start liquidation (vaultState: LIQUIDATING)
|
|
656
|
+
const liquidator = state.liquidator;
|
|
657
|
+
assert(liquidator);
|
|
658
|
+
state.liquidatingVaults.add(vault);
|
|
659
|
+
prioritizedVaults.removeVault(key);
|
|
660
|
+
|
|
661
|
+
return liquidate(
|
|
662
|
+
zcf,
|
|
663
|
+
vault,
|
|
664
|
+
liquidator,
|
|
665
|
+
state.collateralBrand,
|
|
666
|
+
factoryPowers.getGovernedParams().getLiquidationPenalty(),
|
|
667
|
+
)
|
|
668
|
+
.then(accounting => {
|
|
669
|
+
facets.manager.burnAndRecord(accounting.toBurn, vaultSeat);
|
|
670
|
+
|
|
671
|
+
// current values
|
|
672
|
+
|
|
673
|
+
// Sometimes, the AMM will sell less than all the collateral. If there
|
|
674
|
+
// was a shortfall, the investor doesn't keep the change, so we get it.
|
|
675
|
+
// If there was no shortfall, the collateral is returned.
|
|
676
|
+
const collateralPost = vault.getCollateralAmount();
|
|
677
|
+
if (
|
|
678
|
+
!AmountMath.isEmpty(collateralPost) &&
|
|
679
|
+
!AmountMath.isEmpty(accounting.shortfall)
|
|
680
|
+
) {
|
|
681
|
+
// The borrower doesn't get the excess collateral remaining when
|
|
682
|
+
// liquidation results in a shortfall. We currently do nothing with
|
|
683
|
+
// it. We could hold it until it crosses some threshold, then sell it
|
|
684
|
+
// to the AMM, or we could transfer it to the reserve. At least it's
|
|
685
|
+
// visible in the accounting.
|
|
686
|
+
atomicTransfer(zcf, vaultSeat, state.retainedCollateralSeat, {
|
|
687
|
+
Collateral: collateralPost,
|
|
688
|
+
});
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
// Reduce totalCollateral by collateralPre, since all the collateral was
|
|
692
|
+
// sold, returned to the vault owner, or held by the VaultManager.
|
|
693
|
+
state.totalCollateral = AmountMath.subtract(
|
|
694
|
+
state.totalCollateral,
|
|
695
|
+
collateralPre,
|
|
696
|
+
);
|
|
697
|
+
state.totalDebt = AmountMath.subtract(
|
|
698
|
+
state.totalDebt,
|
|
699
|
+
accounting.shortfall,
|
|
700
|
+
);
|
|
701
|
+
|
|
702
|
+
// cumulative values
|
|
703
|
+
state.totalProceedsReceived = AmountMath.add(
|
|
704
|
+
state.totalProceedsReceived,
|
|
705
|
+
accounting.proceeds,
|
|
706
|
+
);
|
|
707
|
+
state.totalOverageReceived = AmountMath.add(
|
|
708
|
+
state.totalOverageReceived,
|
|
709
|
+
accounting.overage,
|
|
710
|
+
);
|
|
711
|
+
state.totalShortfallReceived = AmountMath.add(
|
|
712
|
+
state.totalShortfallReceived,
|
|
713
|
+
accounting.shortfall,
|
|
714
|
+
);
|
|
715
|
+
state.liquidatingVaults.delete(vault);
|
|
716
|
+
trace('liquidated', state.collateralBrand);
|
|
717
|
+
state.numLiquidationsCompleted += 1;
|
|
718
|
+
facets.helper.updateMetrics();
|
|
719
|
+
|
|
720
|
+
if (!AmountMath.isEmpty(accounting.shortfall)) {
|
|
721
|
+
E(factoryPowers.getShortfallReporter())
|
|
722
|
+
.increaseLiquidationShortfall(accounting.shortfall)
|
|
723
|
+
.catch(reason =>
|
|
724
|
+
console.error(
|
|
725
|
+
'liquidateAndRemove failed to increaseLiquidationShortfall',
|
|
726
|
+
reason,
|
|
727
|
+
),
|
|
728
|
+
);
|
|
729
|
+
}
|
|
730
|
+
})
|
|
731
|
+
.catch(e => {
|
|
732
|
+
// XXX should notify interested parties
|
|
733
|
+
console.error('liquidateAndRemove failed with', e);
|
|
734
|
+
throw e;
|
|
735
|
+
});
|
|
736
|
+
},
|
|
610
737
|
},
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
738
|
+
manager: {
|
|
739
|
+
getGovernedParams() {
|
|
740
|
+
const { state } = this;
|
|
741
|
+
const ephemera = provideEphemera(state.collateralBrand);
|
|
742
|
+
assert(ephemera.factoryPowers);
|
|
743
|
+
return ephemera.factoryPowers.getGovernedParams();
|
|
744
|
+
},
|
|
745
|
+
|
|
746
|
+
/**
|
|
747
|
+
* @param {Amount<'nat'>} collateralAmount
|
|
748
|
+
*/
|
|
749
|
+
async maxDebtFor(collateralAmount) {
|
|
750
|
+
const { state } = this;
|
|
751
|
+
const { debtBrand } = state;
|
|
752
|
+
const { priceAuthority, ...ephemera } = provideEphemera(
|
|
753
|
+
state.collateralBrand,
|
|
754
|
+
);
|
|
755
|
+
assert(ephemera.factoryPowers && priceAuthority);
|
|
756
|
+
const quoteAmount = await E(priceAuthority).quoteGiven(
|
|
757
|
+
collateralAmount,
|
|
758
|
+
debtBrand,
|
|
759
|
+
);
|
|
760
|
+
// floorDivide because we want the debt ceiling lower
|
|
761
|
+
return floorDivideBy(
|
|
762
|
+
getAmountOut(quoteAmount),
|
|
763
|
+
ephemera.factoryPowers.getGovernedParams().getLiquidationMargin(),
|
|
764
|
+
);
|
|
765
|
+
},
|
|
766
|
+
/**
|
|
767
|
+
* TODO utility method to turn a callback into non-actual one
|
|
768
|
+
* was type {MintAndReallocate}
|
|
769
|
+
*
|
|
770
|
+
* @param {Amount<'nat'>} toMint
|
|
771
|
+
* @param {Amount<'nat'>} fee
|
|
772
|
+
* @param {ZCFSeat} seat
|
|
773
|
+
* @param {...ZCFSeat} otherSeats
|
|
774
|
+
* @returns {void}
|
|
775
|
+
*/
|
|
776
|
+
mintAndReallocate(toMint, fee, seat, ...otherSeats) {
|
|
777
|
+
const { state } = this;
|
|
778
|
+
const { totalDebt } = state;
|
|
779
|
+
const { factoryPowers } = provideEphemera(state.collateralBrand);
|
|
780
|
+
assert(factoryPowers);
|
|
781
|
+
|
|
782
|
+
checkDebtLimit(
|
|
783
|
+
factoryPowers.getGovernedParams().getDebtLimit(),
|
|
784
|
+
totalDebt,
|
|
785
|
+
toMint,
|
|
786
|
+
);
|
|
787
|
+
factoryPowers.mintAndReallocate(toMint, fee, seat, ...otherSeats);
|
|
788
|
+
state.totalDebt = AmountMath.add(state.totalDebt, toMint);
|
|
789
|
+
},
|
|
790
|
+
/**
|
|
791
|
+
* @param {Amount<'nat'>} toBurn
|
|
792
|
+
* @param {ZCFSeat} seat
|
|
793
|
+
*/
|
|
794
|
+
burnAndRecord(toBurn, seat) {
|
|
795
|
+
const { state } = this;
|
|
796
|
+
const { factoryPowers } = provideEphemera(state.collateralBrand);
|
|
797
|
+
assert(factoryPowers);
|
|
798
|
+
trace('burnAndRecord', state.collateralBrand, {
|
|
799
|
+
toBurn,
|
|
800
|
+
totalDebt: state.totalDebt,
|
|
801
|
+
});
|
|
802
|
+
const { burnDebt } = factoryPowers;
|
|
803
|
+
burnDebt(toBurn, seat);
|
|
804
|
+
state.totalDebt = AmountMath.subtract(state.totalDebt, toBurn);
|
|
805
|
+
},
|
|
806
|
+
getAssetSubscriber() {
|
|
807
|
+
const { storedAssetSubscriber } = provideEphemera(
|
|
808
|
+
this.state.collateralBrand,
|
|
809
|
+
);
|
|
810
|
+
assert(storedAssetSubscriber);
|
|
811
|
+
return storedAssetSubscriber;
|
|
812
|
+
},
|
|
813
|
+
getCollateralBrand() {
|
|
814
|
+
return this.state.collateralBrand;
|
|
815
|
+
},
|
|
816
|
+
getDebtBrand() {
|
|
817
|
+
return this.state.debtBrand;
|
|
818
|
+
},
|
|
819
|
+
/**
|
|
820
|
+
* coefficient on existing debt to calculate new debt
|
|
821
|
+
*/
|
|
822
|
+
getCompoundedInterest() {
|
|
823
|
+
return this.state.compoundedInterest;
|
|
824
|
+
},
|
|
825
|
+
/**
|
|
826
|
+
* Called by a vault when its balances change.
|
|
827
|
+
*
|
|
828
|
+
* @param {NormalizedDebt} oldDebtNormalized
|
|
829
|
+
* @param {Amount<'nat'>} oldCollateral
|
|
830
|
+
* @param {VaultId} vaultId
|
|
831
|
+
* @param {import('./vault.js').VaultPhase} vaultPhase at the end of whatever change updated balances
|
|
832
|
+
* @param {Vault} vault
|
|
833
|
+
*/
|
|
834
|
+
handleBalanceChange(
|
|
835
|
+
oldDebtNormalized,
|
|
836
|
+
oldCollateral,
|
|
837
|
+
vaultId,
|
|
838
|
+
vaultPhase,
|
|
634
839
|
vault,
|
|
635
|
-
|
|
636
|
-
state
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
.then(accounting => {
|
|
640
|
-
facets.manager.burnAndRecord(accounting.toBurn, vaultSeat);
|
|
641
|
-
|
|
642
|
-
// current values
|
|
643
|
-
|
|
644
|
-
// Sometimes, the AMM will sell less than all the collateral. If there
|
|
645
|
-
// was a shortfall, the investor doesn't keep the change, so we get it.
|
|
646
|
-
// If there was no shortfall, the collateral is returned.
|
|
647
|
-
const collateralPost = vault.getCollateralAmount();
|
|
648
|
-
if (
|
|
649
|
-
!AmountMath.isEmpty(collateralPost) &&
|
|
650
|
-
!AmountMath.isEmpty(accounting.shortfall)
|
|
651
|
-
) {
|
|
652
|
-
// The borrower doesn't get the excess collateral remaining when
|
|
653
|
-
// liquidation results in a shortfall. We currently do nothing with
|
|
654
|
-
// it. We could hold it until it crosses some threshold, then sell it
|
|
655
|
-
// to the AMM, or we could transfer it to the reserve. At least it's
|
|
656
|
-
// visible in the accounting.
|
|
657
|
-
atomicTransfer(zcf, vaultSeat, state.retainedCollateralSeat, {
|
|
658
|
-
Collateral: collateralPost,
|
|
659
|
-
});
|
|
660
|
-
}
|
|
840
|
+
) {
|
|
841
|
+
const { state, facets } = this;
|
|
842
|
+
const { prioritizedVaults } = provideEphemera(state.collateralBrand);
|
|
843
|
+
assert(prioritizedVaults);
|
|
661
844
|
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
state.totalDebt = AmountMath.subtract(
|
|
669
|
-
state.totalDebt,
|
|
670
|
-
accounting.shortfall,
|
|
671
|
-
);
|
|
845
|
+
// the manager holds only vaults that can accrue interest or be liquidated;
|
|
846
|
+
// i.e. vaults that have debt. The one exception is at the outset when
|
|
847
|
+
// a vault has been added to the manager but not yet accounted for.
|
|
848
|
+
const settled =
|
|
849
|
+
AmountMath.isEmpty(oldDebtNormalized) &&
|
|
850
|
+
vaultPhase !== Phase.ACTIVE;
|
|
672
851
|
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
);
|
|
682
|
-
state.totalShortfallReceived = AmountMath.add(
|
|
683
|
-
state.totalShortfallReceived,
|
|
684
|
-
accounting.shortfall,
|
|
852
|
+
if (settled) {
|
|
853
|
+
assert(
|
|
854
|
+
!prioritizedVaults.hasVaultByAttributes(
|
|
855
|
+
oldDebtNormalized,
|
|
856
|
+
oldCollateral,
|
|
857
|
+
vaultId,
|
|
858
|
+
),
|
|
859
|
+
'Settled vaults must not be retained in storage',
|
|
685
860
|
);
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
861
|
+
} else {
|
|
862
|
+
const isNew = AmountMath.isEmpty(oldDebtNormalized);
|
|
863
|
+
if (!isNew) {
|
|
864
|
+
// its position in the queue is no longer valid
|
|
865
|
+
|
|
866
|
+
const vaultInStore = prioritizedVaults.removeVaultByAttributes(
|
|
867
|
+
oldDebtNormalized,
|
|
868
|
+
oldCollateral,
|
|
869
|
+
vaultId,
|
|
870
|
+
);
|
|
871
|
+
assert(
|
|
872
|
+
vault === vaultInStore,
|
|
873
|
+
'handleBalanceChange for two different vaults',
|
|
874
|
+
);
|
|
700
875
|
}
|
|
701
|
-
})
|
|
702
|
-
.catch(e => {
|
|
703
|
-
// XXX should notify interested parties
|
|
704
|
-
console.error('liquidateAndRemove failed with', e);
|
|
705
|
-
throw e;
|
|
706
|
-
});
|
|
707
|
-
},
|
|
708
|
-
},
|
|
709
|
-
manager: {
|
|
710
|
-
getGovernedParams() {
|
|
711
|
-
const { state } = this;
|
|
712
|
-
const ephemera = provideEphemera(state.collateralBrand);
|
|
713
|
-
assert(ephemera.factoryPowers);
|
|
714
|
-
return ephemera.factoryPowers.getGovernedParams();
|
|
715
|
-
},
|
|
716
876
|
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
const { debtBrand } = state;
|
|
723
|
-
const { priceAuthority, ...ephemera } = provideEphemera(
|
|
724
|
-
state.collateralBrand,
|
|
725
|
-
);
|
|
726
|
-
assert(ephemera.factoryPowers && priceAuthority);
|
|
727
|
-
const quoteAmount = await E(priceAuthority).quoteGiven(
|
|
728
|
-
collateralAmount,
|
|
729
|
-
debtBrand,
|
|
730
|
-
);
|
|
731
|
-
// floorDivide because we want the debt ceiling lower
|
|
732
|
-
return floorDivideBy(
|
|
733
|
-
getAmountOut(quoteAmount),
|
|
734
|
-
ephemera.factoryPowers.getGovernedParams().getLiquidationMargin(),
|
|
735
|
-
);
|
|
736
|
-
},
|
|
737
|
-
/**
|
|
738
|
-
* TODO utility method to turn a callback into non-actual one
|
|
739
|
-
* was type {MintAndReallocate}
|
|
740
|
-
*
|
|
741
|
-
* @param {Amount<'nat'>} toMint
|
|
742
|
-
* @param {Amount<'nat'>} fee
|
|
743
|
-
* @param {ZCFSeat} seat
|
|
744
|
-
* @param {...ZCFSeat} otherSeats
|
|
745
|
-
* @returns {void}
|
|
746
|
-
*/
|
|
747
|
-
mintAndReallocate(toMint, fee, seat, ...otherSeats) {
|
|
748
|
-
const { state } = this;
|
|
749
|
-
const { totalDebt } = state;
|
|
750
|
-
const { factoryPowers } = provideEphemera(state.collateralBrand);
|
|
751
|
-
assert(factoryPowers);
|
|
752
|
-
|
|
753
|
-
checkDebtLimit(
|
|
754
|
-
factoryPowers.getGovernedParams().getDebtLimit(),
|
|
755
|
-
totalDebt,
|
|
756
|
-
toMint,
|
|
757
|
-
);
|
|
758
|
-
factoryPowers.mintAndReallocate(toMint, fee, seat, ...otherSeats);
|
|
759
|
-
state.totalDebt = AmountMath.add(state.totalDebt, toMint);
|
|
760
|
-
},
|
|
761
|
-
/**
|
|
762
|
-
* @param {Amount<'nat'>} toBurn
|
|
763
|
-
* @param {ZCFSeat} seat
|
|
764
|
-
*/
|
|
765
|
-
burnAndRecord(toBurn, seat) {
|
|
766
|
-
const { state } = this;
|
|
767
|
-
const { factoryPowers } = provideEphemera(state.collateralBrand);
|
|
768
|
-
assert(factoryPowers);
|
|
769
|
-
trace('burnAndRecord', state.collateralBrand, {
|
|
770
|
-
toBurn,
|
|
771
|
-
totalDebt: state.totalDebt,
|
|
772
|
-
});
|
|
773
|
-
const { burnDebt } = factoryPowers;
|
|
774
|
-
burnDebt(toBurn, seat);
|
|
775
|
-
state.totalDebt = AmountMath.subtract(state.totalDebt, toBurn);
|
|
776
|
-
},
|
|
777
|
-
getAssetSubscriber() {
|
|
778
|
-
const { state } = this;
|
|
779
|
-
const { assetSubscriber } = provideEphemera(state.collateralBrand);
|
|
780
|
-
assert(assetSubscriber);
|
|
781
|
-
return assetSubscriber;
|
|
782
|
-
},
|
|
783
|
-
getCollateralBrand() {
|
|
784
|
-
return this.state.collateralBrand;
|
|
785
|
-
},
|
|
786
|
-
getDebtBrand() {
|
|
787
|
-
return this.state.debtBrand;
|
|
788
|
-
},
|
|
789
|
-
/**
|
|
790
|
-
* coefficient on existing debt to calculate new debt
|
|
791
|
-
*/
|
|
792
|
-
getCompoundedInterest() {
|
|
793
|
-
return this.state.compoundedInterest;
|
|
794
|
-
},
|
|
795
|
-
/**
|
|
796
|
-
* Called by a vault when its balances change.
|
|
797
|
-
*
|
|
798
|
-
* @param {NormalizedDebt} oldDebtNormalized
|
|
799
|
-
* @param {Amount<'nat'>} oldCollateral
|
|
800
|
-
* @param {VaultId} vaultId
|
|
801
|
-
* @param {import('./vault.js').VaultPhase} vaultPhase at the end of whatever change updated balances
|
|
802
|
-
* @param {Vault} vault
|
|
803
|
-
*/
|
|
804
|
-
handleBalanceChange(
|
|
805
|
-
oldDebtNormalized,
|
|
806
|
-
oldCollateral,
|
|
807
|
-
vaultId,
|
|
808
|
-
vaultPhase,
|
|
809
|
-
vault,
|
|
810
|
-
) {
|
|
811
|
-
const { state, facets } = this;
|
|
812
|
-
const { prioritizedVaults } = provideEphemera(state.collateralBrand);
|
|
813
|
-
assert(prioritizedVaults);
|
|
814
|
-
|
|
815
|
-
// the manager holds only vaults that can accrue interest or be liquidated;
|
|
816
|
-
// i.e. vaults that have debt. The one exception is at the outset when
|
|
817
|
-
// a vault has been added to the manager but not yet accounted for.
|
|
818
|
-
const settled =
|
|
819
|
-
AmountMath.isEmpty(oldDebtNormalized) && vaultPhase !== Phase.ACTIVE;
|
|
820
|
-
|
|
821
|
-
if (settled) {
|
|
822
|
-
assert(
|
|
823
|
-
!prioritizedVaults.hasVaultByAttributes(
|
|
824
|
-
oldDebtNormalized,
|
|
825
|
-
oldCollateral,
|
|
826
|
-
vaultId,
|
|
827
|
-
),
|
|
828
|
-
'Settled vaults must not be retained in storage',
|
|
829
|
-
);
|
|
830
|
-
} else {
|
|
831
|
-
const isNew = AmountMath.isEmpty(oldDebtNormalized);
|
|
832
|
-
if (!isNew) {
|
|
833
|
-
// its position in the queue is no longer valid
|
|
877
|
+
// replace in queue, but only if it can accrue interest or be liquidated (i.e. has debt).
|
|
878
|
+
// getCurrentDebt() would also work (0x = 0) but require more computation.
|
|
879
|
+
if (!AmountMath.isEmpty(vault.getNormalizedDebt())) {
|
|
880
|
+
prioritizedVaults.addVault(vaultId, vault);
|
|
881
|
+
}
|
|
834
882
|
|
|
835
|
-
|
|
836
|
-
|
|
883
|
+
// totalCollateral += vault's collateral delta (post — pre)
|
|
884
|
+
state.totalCollateral = AmountMath.subtract(
|
|
885
|
+
AmountMath.add(
|
|
886
|
+
state.totalCollateral,
|
|
887
|
+
vault.getCollateralAmount(),
|
|
888
|
+
),
|
|
837
889
|
oldCollateral,
|
|
838
|
-
vaultId,
|
|
839
|
-
);
|
|
840
|
-
assert(
|
|
841
|
-
vault === vaultInStore,
|
|
842
|
-
'handleBalanceChange for two different vaults',
|
|
843
890
|
);
|
|
891
|
+
// debt accounting managed through minting and burning
|
|
892
|
+
facets.helper.updateMetrics();
|
|
844
893
|
}
|
|
894
|
+
},
|
|
895
|
+
},
|
|
896
|
+
self: {
|
|
897
|
+
getGovernedParams() {
|
|
898
|
+
const { state } = this;
|
|
899
|
+
const { factoryPowers } = provideEphemera(state.collateralBrand);
|
|
900
|
+
assert(factoryPowers);
|
|
901
|
+
return factoryPowers.getGovernedParams();
|
|
902
|
+
},
|
|
903
|
+
|
|
904
|
+
/**
|
|
905
|
+
* In extreme situations, system health may require liquidating all vaults.
|
|
906
|
+
* This starts the liquidations all in parallel.
|
|
907
|
+
*/
|
|
908
|
+
async liquidateAll() {
|
|
909
|
+
const {
|
|
910
|
+
state,
|
|
911
|
+
facets: { helper },
|
|
912
|
+
} = this;
|
|
913
|
+
const { prioritizedVaults } = provideEphemera(state.collateralBrand);
|
|
914
|
+
assert(prioritizedVaults);
|
|
915
|
+
const toLiquidate = Array.from(prioritizedVaults.entries()).map(
|
|
916
|
+
entry => helper.liquidateAndRemove(entry),
|
|
917
|
+
);
|
|
918
|
+
await Promise.all(toLiquidate);
|
|
919
|
+
},
|
|
920
|
+
|
|
921
|
+
/**
|
|
922
|
+
* @param {ZCFSeat} seat
|
|
923
|
+
*/
|
|
924
|
+
async makeVaultKit(seat) {
|
|
925
|
+
const {
|
|
926
|
+
state,
|
|
927
|
+
facets: { manager },
|
|
928
|
+
} = this;
|
|
929
|
+
const { marshaller, prioritizedVaults, storageNode, zcf } =
|
|
930
|
+
provideEphemera(state.collateralBrand);
|
|
931
|
+
assert(marshaller, 'makeVaultKit missing marshaller');
|
|
932
|
+
assert(prioritizedVaults, 'makeVaultKit missing prioritizedVaults');
|
|
933
|
+
assert(storageNode, 'makeVaultKit missing storageNode');
|
|
934
|
+
assert(zcf, 'makeVaultKit missing zcf');
|
|
935
|
+
assertProposalShape(seat, {
|
|
936
|
+
give: { Collateral: null },
|
|
937
|
+
want: { Minted: null },
|
|
938
|
+
});
|
|
845
939
|
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
if (!AmountMath.isEmpty(vault.getNormalizedDebt())) {
|
|
849
|
-
prioritizedVaults.addVault(vaultId, vault);
|
|
850
|
-
}
|
|
940
|
+
state.vaultCounter += 1;
|
|
941
|
+
const vaultId = String(state.vaultCounter);
|
|
851
942
|
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
oldCollateral,
|
|
856
|
-
);
|
|
857
|
-
// debt accounting managed through minting and burning
|
|
858
|
-
facets.helper.updateMetrics();
|
|
859
|
-
}
|
|
860
|
-
},
|
|
861
|
-
},
|
|
862
|
-
self: {
|
|
863
|
-
getGovernedParams() {
|
|
864
|
-
const { state } = this;
|
|
865
|
-
const { factoryPowers } = provideEphemera(state.collateralBrand);
|
|
866
|
-
assert(factoryPowers);
|
|
867
|
-
return factoryPowers.getGovernedParams();
|
|
868
|
-
},
|
|
943
|
+
const vaultStorageNode = E(
|
|
944
|
+
E(storageNode).makeChildNode(`vaults`),
|
|
945
|
+
).makeChildNode(`vault${vaultId}`);
|
|
869
946
|
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
facets: { helper },
|
|
878
|
-
} = this;
|
|
879
|
-
const { prioritizedVaults } = provideEphemera(state.collateralBrand);
|
|
880
|
-
assert(prioritizedVaults);
|
|
881
|
-
const toLiquidate = Array.from(prioritizedVaults.entries()).map(entry =>
|
|
882
|
-
helper.liquidateAndRemove(entry),
|
|
883
|
-
);
|
|
884
|
-
await Promise.all(toLiquidate);
|
|
885
|
-
},
|
|
947
|
+
const { self: vault } = makeVault(
|
|
948
|
+
zcf,
|
|
949
|
+
manager,
|
|
950
|
+
vaultId,
|
|
951
|
+
vaultStorageNode,
|
|
952
|
+
marshaller,
|
|
953
|
+
);
|
|
886
954
|
|
|
887
|
-
/**
|
|
888
|
-
* @param {ZCFSeat} seat
|
|
889
|
-
*/
|
|
890
|
-
async makeVaultKit(seat) {
|
|
891
|
-
const {
|
|
892
|
-
state,
|
|
893
|
-
facets: { manager },
|
|
894
|
-
} = this;
|
|
895
|
-
const { marshaller, prioritizedVaults, storageNode, zcf } =
|
|
896
|
-
provideEphemera(state.collateralBrand);
|
|
897
|
-
assert(marshaller, 'makeVaultKit missing marshaller');
|
|
898
|
-
assert(prioritizedVaults, 'makeVaultKit missing prioritizedVaults');
|
|
899
|
-
assert(storageNode, 'makeVaultKit missing storageNode');
|
|
900
|
-
assert(zcf, 'makeVaultKit missing zcf');
|
|
901
|
-
assertProposalShape(seat, {
|
|
902
|
-
give: { Collateral: null },
|
|
903
|
-
want: { Minted: null },
|
|
904
|
-
});
|
|
905
|
-
|
|
906
|
-
state.vaultCounter += 1;
|
|
907
|
-
const vaultId = String(state.vaultCounter);
|
|
908
|
-
|
|
909
|
-
const vaultStorageNode = E(
|
|
910
|
-
E(storageNode).makeChildNode(`vaults`),
|
|
911
|
-
).makeChildNode(`vault${vaultId}`);
|
|
912
|
-
|
|
913
|
-
const vault = makeVault(
|
|
914
|
-
zcf,
|
|
915
|
-
manager,
|
|
916
|
-
vaultId,
|
|
917
|
-
vaultStorageNode,
|
|
918
|
-
marshaller,
|
|
919
|
-
);
|
|
920
|
-
|
|
921
|
-
try {
|
|
922
|
-
// TODO `await` is allowed until the above ordering is fixed
|
|
923
|
-
// eslint-disable-next-line @jessie.js/no-nested-await
|
|
924
|
-
const vaultKit = await vault.initVaultKit(seat, vaultStorageNode);
|
|
925
|
-
// initVaultKit calls back to handleBalanceChange() which will add the
|
|
926
|
-
// vault to prioritizedVaults
|
|
927
|
-
seat.exit();
|
|
928
|
-
return vaultKit;
|
|
929
|
-
} catch (err) {
|
|
930
|
-
// ??? do we still need this cleanup? it won't get into the store unless it has collateral,
|
|
931
|
-
// which should qualify it to be in the store. If we drop this catch then the nested await
|
|
932
|
-
// for `vault.initVaultKit()` goes away.
|
|
933
|
-
|
|
934
|
-
// remove it from the store if it got in
|
|
935
|
-
/** @type {NormalizedDebt} */
|
|
936
|
-
// @ts-expect-error cast
|
|
937
|
-
const normalizedDebt = AmountMath.makeEmpty(state.debtBrand);
|
|
938
|
-
const collateralPre = seat.getCurrentAllocation().Collateral;
|
|
939
955
|
try {
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
);
|
|
956
|
+
// TODO `await` is allowed until the above ordering is fixed
|
|
957
|
+
// eslint-disable-next-line @jessie.js/no-nested-await
|
|
958
|
+
const vaultKit = await vault.initVaultKit(seat, vaultStorageNode);
|
|
959
|
+
// initVaultKit calls back to handleBalanceChange() which will add the
|
|
960
|
+
// vault to prioritizedVaults
|
|
961
|
+
seat.exit();
|
|
962
|
+
return vaultKit;
|
|
963
|
+
} catch (err) {
|
|
964
|
+
// ??? do we still need this cleanup? it won't get into the store unless it has collateral,
|
|
965
|
+
// which should qualify it to be in the store. If we drop this catch then the nested await
|
|
966
|
+
// for `vault.initVaultKit()` goes away.
|
|
967
|
+
|
|
968
|
+
// remove it from the store if it got in
|
|
969
|
+
/** @type {NormalizedDebt} */
|
|
970
|
+
// @ts-expect-error cast
|
|
971
|
+
const normalizedDebt = AmountMath.makeEmpty(state.debtBrand);
|
|
972
|
+
const collateralPre = seat.getCurrentAllocation().Collateral;
|
|
973
|
+
try {
|
|
974
|
+
prioritizedVaults.removeVaultByAttributes(
|
|
975
|
+
normalizedDebt,
|
|
976
|
+
collateralPre,
|
|
977
|
+
vaultId,
|
|
978
|
+
);
|
|
979
|
+
console.error(
|
|
980
|
+
'removed vault',
|
|
981
|
+
vaultId,
|
|
982
|
+
'after initVaultKit failure',
|
|
983
|
+
);
|
|
984
|
+
} catch {
|
|
985
|
+
console.error(
|
|
986
|
+
'vault',
|
|
987
|
+
vaultId,
|
|
988
|
+
'never stored during initVaultKit failure',
|
|
989
|
+
);
|
|
990
|
+
}
|
|
991
|
+
throw err;
|
|
956
992
|
}
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
const {
|
|
972
|
-
ammPublicFacet,
|
|
973
|
-
priceAuthority,
|
|
974
|
-
reservePublicFacet,
|
|
975
|
-
timerService,
|
|
976
|
-
} = zcf.getTerms();
|
|
977
|
-
const zoe = zcf.getZoeService();
|
|
978
|
-
const collateralIssuer = zcf.getIssuerForBrand(collateralBrand);
|
|
979
|
-
const debtIssuer = zcf.getIssuerForBrand(debtBrand);
|
|
980
|
-
trace('setup liquidator', state.collateralBrand, {
|
|
981
|
-
debtBrand,
|
|
982
|
-
debtIssuer,
|
|
983
|
-
collateralBrand,
|
|
984
|
-
liquidationTerms,
|
|
985
|
-
});
|
|
986
|
-
const { creatorFacet, instance } = await E(zoe).startInstance(
|
|
987
|
-
liquidationInstall,
|
|
988
|
-
harden({ Minted: debtIssuer, Collateral: collateralIssuer }),
|
|
989
|
-
harden({
|
|
990
|
-
...liquidationTerms,
|
|
991
|
-
amm: ammPublicFacet,
|
|
992
|
-
debtBrand,
|
|
993
|
-
reservePublicFacet,
|
|
993
|
+
},
|
|
994
|
+
|
|
995
|
+
/**
|
|
996
|
+
*
|
|
997
|
+
* @param {Installation} liquidationInstall
|
|
998
|
+
* @param {object} liquidationTerms
|
|
999
|
+
*/
|
|
1000
|
+
async setupLiquidator(liquidationInstall, liquidationTerms) {
|
|
1001
|
+
const { state, facets } = this;
|
|
1002
|
+
const { zcf } = provideEphemera(state.collateralBrand);
|
|
1003
|
+
assert(zcf);
|
|
1004
|
+
const { debtBrand, collateralBrand } = state;
|
|
1005
|
+
const {
|
|
1006
|
+
ammPublicFacet,
|
|
994
1007
|
priceAuthority,
|
|
1008
|
+
reservePublicFacet,
|
|
995
1009
|
timerService,
|
|
996
|
-
})
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1010
|
+
} = zcf.getTerms();
|
|
1011
|
+
const zoe = zcf.getZoeService();
|
|
1012
|
+
const collateralIssuer = zcf.getIssuerForBrand(collateralBrand);
|
|
1013
|
+
const debtIssuer = zcf.getIssuerForBrand(debtBrand);
|
|
1014
|
+
trace('setup liquidator', state.collateralBrand, {
|
|
1015
|
+
debtBrand,
|
|
1016
|
+
debtIssuer,
|
|
1017
|
+
collateralBrand,
|
|
1018
|
+
liquidationTerms,
|
|
1019
|
+
});
|
|
1020
|
+
const { creatorFacet, instance } = await E(zoe).startInstance(
|
|
1021
|
+
liquidationInstall,
|
|
1022
|
+
harden({ Minted: debtIssuer, Collateral: collateralIssuer }),
|
|
1023
|
+
harden({
|
|
1024
|
+
...liquidationTerms,
|
|
1025
|
+
amm: ammPublicFacet,
|
|
1026
|
+
debtBrand,
|
|
1027
|
+
reservePublicFacet,
|
|
1028
|
+
priceAuthority,
|
|
1029
|
+
timerService,
|
|
1030
|
+
}),
|
|
1031
|
+
);
|
|
1032
|
+
trace('setup liquidator complete', state.collateralBrand, {
|
|
1033
|
+
instance,
|
|
1034
|
+
old: state.liquidatorInstance,
|
|
1035
|
+
equal: state.liquidatorInstance === instance,
|
|
1036
|
+
});
|
|
1037
|
+
state.liquidatorInstance = instance;
|
|
1038
|
+
state.liquidator = creatorFacet;
|
|
1039
|
+
facets.helper.assetNotify();
|
|
1040
|
+
},
|
|
1041
|
+
|
|
1042
|
+
async getCollateralQuote() {
|
|
1043
|
+
const { state } = this;
|
|
1044
|
+
const { priceAuthority } = provideEphemera(state.collateralBrand);
|
|
1045
|
+
assert(priceAuthority);
|
|
1046
|
+
|
|
1047
|
+
const { debtBrand } = state;
|
|
1048
|
+
// get a quote for one unit of the collateral
|
|
1049
|
+
const collateralUnit = await unitAmount(state.collateralBrand);
|
|
1050
|
+
return E(priceAuthority).quoteGiven(collateralUnit, debtBrand);
|
|
1051
|
+
},
|
|
1052
|
+
|
|
1053
|
+
getPublicFacet() {
|
|
1054
|
+
return this.facets.collateral;
|
|
1055
|
+
},
|
|
1021
1056
|
},
|
|
1022
1057
|
},
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1058
|
+
{
|
|
1059
|
+
finish,
|
|
1060
|
+
},
|
|
1061
|
+
);
|
|
1062
|
+
};
|
|
1028
1063
|
|
|
1029
1064
|
/**
|
|
1065
|
+
* @typedef {ReturnType<ReturnType<typeof vivifyVaultManagerKit>>['self']} VaultManager
|
|
1030
1066
|
* Each VaultManager manages a single collateral type.
|
|
1031
1067
|
*
|
|
1032
1068
|
* It manages some number of outstanding loans, each called a Vault, for which
|
|
1033
1069
|
* the collateral is provided in exchange for borrowed Minted.
|
|
1034
|
-
*
|
|
1035
|
-
* @param {ZCF} zcf
|
|
1036
|
-
* @param {ZCFMint<'nat'>} debtMint
|
|
1037
|
-
* @param {Brand} collateralBrand
|
|
1038
|
-
* @param {ERef<PriceAuthority>} priceAuthority
|
|
1039
|
-
* @param {import('./vaultDirector.js').FactoryPowersFacet} factoryPowers
|
|
1040
|
-
* @param {ERef<TimerService>} timerService
|
|
1041
|
-
* @param {Timestamp} startTimeStamp
|
|
1042
1070
|
*/
|
|
1043
|
-
export const makeVaultManager = pickFacet(makeVaultManagerKit, 'self');
|
|
1044
|
-
|
|
1045
|
-
/** @typedef {ReturnType<typeof makeVaultManagerKit>['manager']} VaultKitManager */
|
|
1046
|
-
/** @typedef {ReturnType<typeof makeVaultManager>} VaultManager */
|
|
1047
1071
|
/** @typedef {ReturnType<VaultManager['getPublicFacet']>} CollateralManager */
|