@agoric/inter-protocol 0.13.2-dev-8f9e16b.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 +127 -127
- 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 +73 -73
- 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
|
@@ -1,31 +1,26 @@
|
|
|
1
|
-
import '@agoric/
|
|
2
|
-
|
|
1
|
+
import { AmountMath, AmountShape } from '@agoric/ertp';
|
|
2
|
+
import { StorageNodeShape } from '@agoric/notifier/src/typeGuards.js';
|
|
3
|
+
import { M, vivifyFarClassKit } from '@agoric/vat-data';
|
|
3
4
|
import {
|
|
4
5
|
assertProposalShape,
|
|
5
|
-
|
|
6
|
+
atomicTransfer,
|
|
6
7
|
ceilMultiplyBy,
|
|
7
8
|
floorMultiplyBy,
|
|
8
|
-
|
|
9
|
+
makeRatioFromAmounts,
|
|
9
10
|
} from '@agoric/zoe/src/contractSupport/index.js';
|
|
10
|
-
import { AmountMath, AmountShape } from '@agoric/ertp';
|
|
11
|
-
import { StorageNodeShape } from '@agoric/notifier/src/typeGuards.js';
|
|
12
|
-
import {
|
|
13
|
-
defineDurableFarClassKit,
|
|
14
|
-
M,
|
|
15
|
-
makeKindHandle,
|
|
16
|
-
pickFacet,
|
|
17
|
-
} from '@agoric/vat-data';
|
|
18
11
|
import { SeatShape } from '@agoric/zoe/src/typeGuards.js';
|
|
19
|
-
import { makeTracer } from '../makeTracer.js';
|
|
20
|
-
import { calculateCurrentDebt, reverseInterest } from '../interest-math.js';
|
|
21
|
-
import { makeVaultKit } from './vaultKit.js';
|
|
22
12
|
import {
|
|
23
13
|
addSubtract,
|
|
24
14
|
assertOnlyKeys,
|
|
25
15
|
makeEphemeraProvider,
|
|
26
16
|
stageDelta,
|
|
27
17
|
} from '../contractSupport.js';
|
|
18
|
+
import { calculateCurrentDebt, reverseInterest } from '../interest-math.js';
|
|
19
|
+
import { makeTracer } from '../makeTracer.js';
|
|
28
20
|
import { UnguardedHelperI } from '../typeGuards.js';
|
|
21
|
+
import { vivifyVaultKit } from './vaultKit.js';
|
|
22
|
+
|
|
23
|
+
import '@agoric/zoe/exported.js';
|
|
29
24
|
|
|
30
25
|
const { quote: q, Fail } = assert;
|
|
31
26
|
|
|
@@ -217,619 +212,638 @@ export const VaultI = M.interface('Vault', {
|
|
|
217
212
|
makeTransferInvitation: M.call().returns(M.promise()),
|
|
218
213
|
});
|
|
219
214
|
|
|
220
|
-
const
|
|
221
|
-
const
|
|
222
|
-
vaultKind,
|
|
223
|
-
{
|
|
224
|
-
helper: UnguardedHelperI,
|
|
225
|
-
self: VaultI,
|
|
226
|
-
},
|
|
227
|
-
initState,
|
|
228
|
-
{
|
|
229
|
-
helper: {
|
|
230
|
-
// #region Computed constants
|
|
231
|
-
collateralBrand() {
|
|
232
|
-
return this.state.manager.getCollateralBrand();
|
|
233
|
-
},
|
|
234
|
-
debtBrand() {
|
|
235
|
-
return this.state.manager.getDebtBrand();
|
|
236
|
-
},
|
|
237
|
-
|
|
238
|
-
emptyCollateral() {
|
|
239
|
-
return AmountMath.makeEmpty(this.facets.helper.collateralBrand());
|
|
240
|
-
},
|
|
241
|
-
emptyDebt() {
|
|
242
|
-
return AmountMath.makeEmpty(this.facets.helper.debtBrand());
|
|
243
|
-
},
|
|
244
|
-
// #endregion
|
|
245
|
-
|
|
246
|
-
// #region Phase logic
|
|
247
|
-
/**
|
|
248
|
-
* @param {VaultPhase} newPhase
|
|
249
|
-
*/
|
|
250
|
-
assignPhase(newPhase) {
|
|
251
|
-
const { state } = this;
|
|
252
|
-
|
|
253
|
-
const { phase } = state;
|
|
254
|
-
const validNewPhases = validTransitions[phase];
|
|
255
|
-
validNewPhases.includes(newPhase) ||
|
|
256
|
-
Fail`Vault cannot transition from ${q(phase)} to ${q(newPhase)}`;
|
|
257
|
-
state.phase = newPhase;
|
|
258
|
-
},
|
|
259
|
-
|
|
260
|
-
assertActive() {
|
|
261
|
-
const { phase } = this.state;
|
|
262
|
-
assert(phase === Phase.ACTIVE);
|
|
263
|
-
},
|
|
264
|
-
|
|
265
|
-
assertCloseable() {
|
|
266
|
-
const { phase } = this.state;
|
|
267
|
-
phase === Phase.ACTIVE ||
|
|
268
|
-
phase === Phase.LIQUIDATED ||
|
|
269
|
-
Fail`to be closed a vault must be active or liquidated, not ${phase}`;
|
|
270
|
-
},
|
|
271
|
-
// #endregion
|
|
272
|
-
|
|
273
|
-
/**
|
|
274
|
-
* Called whenever the debt is paid or created through a transaction,
|
|
275
|
-
* but not for interest accrual.
|
|
276
|
-
*
|
|
277
|
-
* @param {Amount<'nat'>} newDebt - principal and all accrued interest
|
|
278
|
-
*/
|
|
279
|
-
updateDebtSnapshot(newDebt) {
|
|
280
|
-
const { state } = this;
|
|
281
|
-
|
|
282
|
-
// update local state
|
|
283
|
-
state.debtSnapshot = newDebt;
|
|
284
|
-
state.interestSnapshot = state.manager.getCompoundedInterest();
|
|
285
|
-
},
|
|
215
|
+
export const vivifyVault = baggage => {
|
|
216
|
+
const makeVaultKit = vivifyVaultKit(baggage);
|
|
286
217
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
state.
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
)
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
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
|
-
|
|
353
|
-
|
|
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
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
}
|
|
393
|
-
|
|
218
|
+
// TODO find a way to not have to indent a level deeper than defineDurableFarClassKit does
|
|
219
|
+
const maker = vivifyFarClassKit(
|
|
220
|
+
baggage,
|
|
221
|
+
'Vault',
|
|
222
|
+
{
|
|
223
|
+
helper: UnguardedHelperI,
|
|
224
|
+
self: VaultI,
|
|
225
|
+
},
|
|
226
|
+
initState,
|
|
227
|
+
{
|
|
228
|
+
helper: {
|
|
229
|
+
// #region Computed constants
|
|
230
|
+
collateralBrand() {
|
|
231
|
+
return this.state.manager.getCollateralBrand();
|
|
232
|
+
},
|
|
233
|
+
debtBrand() {
|
|
234
|
+
return this.state.manager.getDebtBrand();
|
|
235
|
+
},
|
|
236
|
+
|
|
237
|
+
emptyCollateral() {
|
|
238
|
+
return AmountMath.makeEmpty(this.facets.helper.collateralBrand());
|
|
239
|
+
},
|
|
240
|
+
emptyDebt() {
|
|
241
|
+
return AmountMath.makeEmpty(this.facets.helper.debtBrand());
|
|
242
|
+
},
|
|
243
|
+
// #endregion
|
|
244
|
+
|
|
245
|
+
// #region Phase logic
|
|
246
|
+
/**
|
|
247
|
+
* @param {VaultPhase} newPhase
|
|
248
|
+
*/
|
|
249
|
+
assignPhase(newPhase) {
|
|
250
|
+
const { state } = this;
|
|
251
|
+
|
|
252
|
+
const { phase } = state;
|
|
253
|
+
const validNewPhases = validTransitions[phase];
|
|
254
|
+
validNewPhases.includes(newPhase) ||
|
|
255
|
+
Fail`Vault cannot transition from ${q(phase)} to ${q(newPhase)}`;
|
|
256
|
+
state.phase = newPhase;
|
|
257
|
+
},
|
|
258
|
+
|
|
259
|
+
assertActive() {
|
|
260
|
+
const { phase } = this.state;
|
|
261
|
+
assert(phase === Phase.ACTIVE);
|
|
262
|
+
},
|
|
263
|
+
|
|
264
|
+
assertCloseable() {
|
|
265
|
+
const { phase } = this.state;
|
|
266
|
+
phase === Phase.ACTIVE ||
|
|
267
|
+
phase === Phase.LIQUIDATED ||
|
|
268
|
+
Fail`to be closed a vault must be active or liquidated, not ${phase}`;
|
|
269
|
+
},
|
|
270
|
+
// #endregion
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Called whenever the debt is paid or created through a transaction,
|
|
274
|
+
* but not for interest accrual.
|
|
275
|
+
*
|
|
276
|
+
* @param {Amount<'nat'>} newDebt - principal and all accrued interest
|
|
277
|
+
*/
|
|
278
|
+
updateDebtSnapshot(newDebt) {
|
|
279
|
+
const { state } = this;
|
|
280
|
+
|
|
281
|
+
// update local state
|
|
282
|
+
state.debtSnapshot = newDebt;
|
|
283
|
+
state.interestSnapshot = state.manager.getCompoundedInterest();
|
|
284
|
+
},
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Update the debt balance and propagate upwards to
|
|
288
|
+
* maintain aggregate debt and liquidation order.
|
|
289
|
+
*
|
|
290
|
+
* @param {NormalizedDebt} oldDebtNormalized - prior principal and all accrued interest, normalized to the launch of the vaultManager
|
|
291
|
+
* @param {Amount<'nat'>} oldCollateral - actual collateral
|
|
292
|
+
* @param {Amount<'nat'>} newDebtActual - actual principal and all accrued interest
|
|
293
|
+
*/
|
|
294
|
+
updateDebtAccounting(oldDebtNormalized, oldCollateral, newDebtActual) {
|
|
295
|
+
const { state, facets } = this;
|
|
296
|
+
const { helper } = facets;
|
|
297
|
+
helper.updateDebtSnapshot(newDebtActual);
|
|
298
|
+
// notify manager so it can notify and clean up as appropriate
|
|
299
|
+
state.manager.handleBalanceChange(
|
|
300
|
+
oldDebtNormalized,
|
|
301
|
+
oldCollateral,
|
|
302
|
+
state.idInManager,
|
|
303
|
+
state.phase,
|
|
304
|
+
facets.self,
|
|
305
|
+
);
|
|
306
|
+
},
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
*
|
|
310
|
+
* @param {ZCFSeat} seat
|
|
311
|
+
*/
|
|
312
|
+
getCollateralAllocated(seat) {
|
|
313
|
+
return seat.getAmountAllocated(
|
|
314
|
+
'Collateral',
|
|
315
|
+
this.facets.helper.collateralBrand(),
|
|
316
|
+
);
|
|
317
|
+
},
|
|
318
|
+
getMintedAllocated(seat) {
|
|
319
|
+
return seat.getAmountAllocated(
|
|
320
|
+
'Minted',
|
|
321
|
+
this.facets.helper.debtBrand(),
|
|
322
|
+
);
|
|
323
|
+
},
|
|
324
|
+
|
|
325
|
+
assertVaultHoldsNoMinted() {
|
|
326
|
+
const { state, facets } = this;
|
|
327
|
+
const { vaultSeat } = state;
|
|
328
|
+
AmountMath.isEmpty(facets.helper.getMintedAllocated(vaultSeat)) ||
|
|
329
|
+
Fail`Vault should be empty of Minted`;
|
|
330
|
+
},
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
*
|
|
334
|
+
* @param {Amount<'nat'>} collateralAmount
|
|
335
|
+
* @param {Amount<'nat'>} proposedRunDebt
|
|
336
|
+
*/
|
|
337
|
+
async assertSufficientCollateral(collateralAmount, proposedRunDebt) {
|
|
338
|
+
const { state, facets } = this;
|
|
339
|
+
const maxRun = await state.manager.maxDebtFor(collateralAmount);
|
|
340
|
+
AmountMath.isGTE(
|
|
341
|
+
maxRun,
|
|
342
|
+
proposedRunDebt,
|
|
343
|
+
facets.helper.debtBrand(),
|
|
344
|
+
) ||
|
|
345
|
+
Fail`Requested ${q(proposedRunDebt)} exceeds max ${q(
|
|
346
|
+
maxRun,
|
|
347
|
+
)} for ${q(collateralAmount)} collateral`;
|
|
348
|
+
},
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
*
|
|
352
|
+
* @param {HolderPhase} newPhase
|
|
353
|
+
*/
|
|
354
|
+
getStateSnapshot(newPhase) {
|
|
355
|
+
const { state, facets } = this;
|
|
356
|
+
|
|
357
|
+
const { debtSnapshot: debt, interestSnapshot: interest } = state;
|
|
358
|
+
/** @type {VaultNotification} */
|
|
359
|
+
return harden({
|
|
360
|
+
debtSnapshot: { debt, interest },
|
|
361
|
+
locked: facets.self.getCollateralAmount(),
|
|
362
|
+
// newPhase param is so that makeTransferInvitation can finish without setting the vault's phase
|
|
363
|
+
// TODO refactor https://github.com/Agoric/agoric-sdk/issues/4415
|
|
364
|
+
vaultState: newPhase,
|
|
365
|
+
});
|
|
366
|
+
},
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* call this whenever anything changes!
|
|
370
|
+
*/
|
|
371
|
+
updateUiState() {
|
|
372
|
+
const { state, facets } = this;
|
|
373
|
+
const ephemera = provideEphemera(state.idInManager);
|
|
374
|
+
const { outerUpdater } = ephemera;
|
|
375
|
+
if (!outerUpdater) {
|
|
376
|
+
// It's not an error to change to liquidating during transfer
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
const { phase } = state;
|
|
380
|
+
const uiState = facets.helper.getStateSnapshot(phase);
|
|
381
|
+
trace('updateUiState', state.idInManager, uiState);
|
|
382
|
+
|
|
383
|
+
switch (phase) {
|
|
384
|
+
case Phase.ACTIVE:
|
|
385
|
+
case Phase.LIQUIDATING:
|
|
386
|
+
case Phase.LIQUIDATED:
|
|
387
|
+
outerUpdater.publish(uiState);
|
|
388
|
+
break;
|
|
389
|
+
case Phase.CLOSED:
|
|
390
|
+
outerUpdater.finish(uiState);
|
|
391
|
+
ephemera.outerUpdater = null;
|
|
392
|
+
break;
|
|
393
|
+
default:
|
|
394
|
+
throw Error(`unreachable vault phase: ${phase}`);
|
|
395
|
+
}
|
|
396
|
+
},
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* @param {ZCFSeat} seat
|
|
400
|
+
*/
|
|
401
|
+
async closeHook(seat) {
|
|
402
|
+
const { state, facets } = this;
|
|
403
|
+
|
|
404
|
+
const { self, helper } = facets;
|
|
405
|
+
helper.assertCloseable();
|
|
406
|
+
const { phase, vaultSeat } = state;
|
|
407
|
+
const { zcf } = provideEphemera(state.idInManager);
|
|
408
|
+
|
|
409
|
+
// Held as keys for cleanup in the manager
|
|
410
|
+
const oldDebtNormalized = self.getNormalizedDebt();
|
|
411
|
+
const oldCollateral = self.getCollateralAmount();
|
|
412
|
+
|
|
413
|
+
if (phase === Phase.ACTIVE) {
|
|
414
|
+
assertProposalShape(seat, {
|
|
415
|
+
give: { Minted: null },
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
// you're paying off the debt, you get everything back.
|
|
419
|
+
const debt = self.getCurrentDebt();
|
|
420
|
+
const {
|
|
421
|
+
give: { Minted: given },
|
|
422
|
+
} = seat.getProposal();
|
|
423
|
+
|
|
424
|
+
// you must pay off the entire remainder but if you offer too much, we won't
|
|
425
|
+
// take more than you owe
|
|
426
|
+
AmountMath.isGTE(given, debt) ||
|
|
427
|
+
Fail`Offer ${given} is not sufficient to pay off debt ${debt}`;
|
|
428
|
+
|
|
429
|
+
// Return any overpayment
|
|
430
|
+
atomicTransfer(
|
|
431
|
+
zcf,
|
|
432
|
+
vaultSeat,
|
|
433
|
+
seat,
|
|
434
|
+
vaultSeat.getCurrentAllocation(),
|
|
435
|
+
);
|
|
436
|
+
|
|
437
|
+
state.manager.burnAndRecord(debt, seat);
|
|
438
|
+
} else if (phase === Phase.LIQUIDATED) {
|
|
439
|
+
// Simply reallocate vault assets to the offer seat.
|
|
440
|
+
// Don't take anything from the offer, even if vault is underwater.
|
|
441
|
+
// TODO verify that returning Minted here doesn't mess up debt limits
|
|
442
|
+
|
|
443
|
+
atomicTransfer(
|
|
444
|
+
zcf,
|
|
445
|
+
vaultSeat,
|
|
446
|
+
seat,
|
|
447
|
+
vaultSeat.getCurrentAllocation(),
|
|
448
|
+
);
|
|
449
|
+
} else {
|
|
450
|
+
throw new Error('only active and liquidated vaults can be closed');
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
seat.exit();
|
|
454
|
+
helper.assignPhase(Phase.CLOSED);
|
|
455
|
+
helper.updateDebtSnapshot(helper.emptyDebt());
|
|
456
|
+
helper.updateUiState();
|
|
457
|
+
|
|
458
|
+
helper.assertVaultHoldsNoMinted();
|
|
459
|
+
vaultSeat.exit();
|
|
460
|
+
|
|
461
|
+
state.manager.handleBalanceChange(
|
|
462
|
+
oldDebtNormalized,
|
|
463
|
+
oldCollateral,
|
|
464
|
+
state.idInManager,
|
|
465
|
+
state.phase,
|
|
466
|
+
facets.self,
|
|
467
|
+
);
|
|
394
468
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
469
|
+
return 'your loan is closed, thank you for your business';
|
|
470
|
+
},
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Calculate the fee, the amount to mint and the resulting debt.
|
|
474
|
+
* The give and the want together reflect a delta, where typically
|
|
475
|
+
* one is zero because they come from the gave/want of an offer
|
|
476
|
+
* proposal. If the `want` is zero, the `fee` will also be zero,
|
|
477
|
+
* so the simple math works.
|
|
478
|
+
*
|
|
479
|
+
* @param {Amount<'nat'>} currentDebt
|
|
480
|
+
* @param {Amount<'nat'>} giveAmount
|
|
481
|
+
* @param {Amount<'nat'>} wantAmount
|
|
482
|
+
*/
|
|
483
|
+
loanFee(currentDebt, giveAmount, wantAmount) {
|
|
484
|
+
const { state } = this;
|
|
485
|
+
|
|
486
|
+
const fee = ceilMultiplyBy(
|
|
487
|
+
wantAmount,
|
|
488
|
+
state.manager.getGovernedParams().getLoanFee(),
|
|
489
|
+
);
|
|
490
|
+
const toMint = AmountMath.add(wantAmount, fee);
|
|
491
|
+
const newDebt = addSubtract(currentDebt, toMint, giveAmount);
|
|
492
|
+
return { newDebt, toMint, fee };
|
|
493
|
+
},
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* Adjust principal and collateral (atomically for offer safety)
|
|
497
|
+
*
|
|
498
|
+
* @param {ZCFSeat} clientSeat
|
|
499
|
+
* @returns {Promise<string>} success message
|
|
500
|
+
*/
|
|
501
|
+
async adjustBalancesHook(clientSeat) {
|
|
502
|
+
const { state, facets } = this;
|
|
503
|
+
|
|
504
|
+
const { self, helper } = facets;
|
|
505
|
+
const ephemera = provideEphemera(state.idInManager);
|
|
506
|
+
const { outerUpdater: updaterPre } = ephemera;
|
|
507
|
+
const { vaultSeat } = state;
|
|
508
|
+
const proposal = clientSeat.getProposal();
|
|
509
|
+
assertOnlyKeys(proposal, ['Collateral', 'Minted']);
|
|
510
|
+
|
|
511
|
+
const allEmpty = amounts => {
|
|
512
|
+
return amounts.every(a => AmountMath.isEmpty(a));
|
|
513
|
+
};
|
|
514
|
+
|
|
515
|
+
const normalizedDebtPre = self.getNormalizedDebt();
|
|
516
|
+
const collateralPre = helper.getCollateralAllocated(vaultSeat);
|
|
517
|
+
|
|
518
|
+
const giveColl = proposal.give.Collateral || helper.emptyCollateral();
|
|
519
|
+
const wantColl = proposal.want.Collateral || helper.emptyCollateral();
|
|
520
|
+
|
|
521
|
+
const newCollateralPre = addSubtract(
|
|
522
|
+
collateralPre,
|
|
523
|
+
giveColl,
|
|
524
|
+
wantColl,
|
|
525
|
+
);
|
|
526
|
+
// max debt supported by current Collateral as modified by proposal
|
|
527
|
+
const maxDebtPre = await state.manager.maxDebtFor(newCollateralPre);
|
|
528
|
+
updaterPre === ephemera.outerUpdater ||
|
|
529
|
+
Fail`Transfer during vault adjustment`;
|
|
530
|
+
helper.assertActive();
|
|
400
531
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
532
|
+
// After the `await`, we retrieve the vault's allocations again,
|
|
533
|
+
// so we can compare to the debt limit based on the new values.
|
|
534
|
+
const collateral = helper.getCollateralAllocated(vaultSeat);
|
|
535
|
+
const newCollateral = addSubtract(collateral, giveColl, wantColl);
|
|
405
536
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
537
|
+
const debt = self.getCurrentDebt();
|
|
538
|
+
const giveMinted = AmountMath.min(
|
|
539
|
+
proposal.give.Minted || helper.emptyDebt(),
|
|
540
|
+
debt,
|
|
541
|
+
);
|
|
542
|
+
const wantMinted = proposal.want.Minted || helper.emptyDebt();
|
|
543
|
+
if (allEmpty([giveColl, giveMinted, wantColl, wantMinted])) {
|
|
544
|
+
clientSeat.exit();
|
|
545
|
+
return 'no transaction, as requested';
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// Calculate the fee, the amount to mint and the resulting debt. We'll
|
|
549
|
+
// verify that the target debt doesn't violate the collateralization ratio,
|
|
550
|
+
// then mint, reallocate, and burn.
|
|
551
|
+
const { newDebt, fee, toMint } = helper.loanFee(
|
|
552
|
+
debt,
|
|
553
|
+
giveMinted,
|
|
554
|
+
wantMinted,
|
|
555
|
+
);
|
|
409
556
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
557
|
+
trace('adjustBalancesHook', state.idInManager, {
|
|
558
|
+
newCollateralPre,
|
|
559
|
+
newCollateral,
|
|
560
|
+
fee,
|
|
561
|
+
toMint,
|
|
562
|
+
newDebt,
|
|
413
563
|
});
|
|
414
564
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
// you must pay off the entire remainder but if you offer too much, we won't
|
|
422
|
-
// take more than you owe
|
|
423
|
-
AmountMath.isGTE(given, debt) ||
|
|
424
|
-
Fail`Offer ${given} is not sufficient to pay off debt ${debt}`;
|
|
565
|
+
if (
|
|
566
|
+
checkRestart(newCollateralPre, maxDebtPre, newCollateral, newDebt)
|
|
567
|
+
) {
|
|
568
|
+
return helper.adjustBalancesHook(clientSeat);
|
|
569
|
+
}
|
|
425
570
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
571
|
+
stageDelta(clientSeat, vaultSeat, giveColl, wantColl, 'Collateral');
|
|
572
|
+
// `wantMinted` is allocated in the reallocate and mint operation, and so not here
|
|
573
|
+
stageDelta(
|
|
574
|
+
clientSeat,
|
|
429
575
|
vaultSeat,
|
|
430
|
-
|
|
431
|
-
|
|
576
|
+
giveMinted,
|
|
577
|
+
helper.emptyDebt(),
|
|
578
|
+
'Minted',
|
|
432
579
|
);
|
|
433
580
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
seat,
|
|
444
|
-
vaultSeat.getCurrentAllocation(),
|
|
581
|
+
/** @type {Array<ZCFSeat>} */
|
|
582
|
+
const vaultSeatOpt = allEmpty([giveColl, giveMinted, wantColl])
|
|
583
|
+
? []
|
|
584
|
+
: [vaultSeat];
|
|
585
|
+
state.manager.mintAndReallocate(
|
|
586
|
+
toMint,
|
|
587
|
+
fee,
|
|
588
|
+
clientSeat,
|
|
589
|
+
...vaultSeatOpt,
|
|
445
590
|
);
|
|
446
|
-
} else {
|
|
447
|
-
throw new Error('only active and liquidated vaults can be closed');
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
seat.exit();
|
|
451
|
-
helper.assignPhase(Phase.CLOSED);
|
|
452
|
-
helper.updateDebtSnapshot(helper.emptyDebt());
|
|
453
|
-
helper.updateUiState();
|
|
454
|
-
|
|
455
|
-
helper.assertVaultHoldsNoMinted();
|
|
456
|
-
vaultSeat.exit();
|
|
457
|
-
|
|
458
|
-
state.manager.handleBalanceChange(
|
|
459
|
-
oldDebtNormalized,
|
|
460
|
-
oldCollateral,
|
|
461
|
-
state.idInManager,
|
|
462
|
-
state.phase,
|
|
463
|
-
facets.self,
|
|
464
|
-
);
|
|
465
|
-
|
|
466
|
-
return 'your loan is closed, thank you for your business';
|
|
467
|
-
},
|
|
468
591
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
* @param {Amount<'nat'>} giveAmount
|
|
478
|
-
* @param {Amount<'nat'>} wantAmount
|
|
479
|
-
*/
|
|
480
|
-
loanFee(currentDebt, giveAmount, wantAmount) {
|
|
481
|
-
const { state } = this;
|
|
482
|
-
|
|
483
|
-
const fee = ceilMultiplyBy(
|
|
484
|
-
wantAmount,
|
|
485
|
-
state.manager.getGovernedParams().getLoanFee(),
|
|
486
|
-
);
|
|
487
|
-
const toMint = AmountMath.add(wantAmount, fee);
|
|
488
|
-
const newDebt = addSubtract(currentDebt, toMint, giveAmount);
|
|
489
|
-
return { newDebt, toMint, fee };
|
|
490
|
-
},
|
|
592
|
+
// parent needs to know about the change in debt
|
|
593
|
+
helper.updateDebtAccounting(
|
|
594
|
+
normalizedDebtPre,
|
|
595
|
+
collateralPre,
|
|
596
|
+
newDebt,
|
|
597
|
+
);
|
|
598
|
+
state.manager.burnAndRecord(giveMinted, vaultSeat);
|
|
599
|
+
helper.assertVaultHoldsNoMinted();
|
|
491
600
|
|
|
492
|
-
|
|
493
|
-
* Adjust principal and collateral (atomically for offer safety)
|
|
494
|
-
*
|
|
495
|
-
* @param {ZCFSeat} clientSeat
|
|
496
|
-
* @returns {Promise<string>} success message
|
|
497
|
-
*/
|
|
498
|
-
async adjustBalancesHook(clientSeat) {
|
|
499
|
-
const { state, facets } = this;
|
|
500
|
-
|
|
501
|
-
const { self, helper } = facets;
|
|
502
|
-
const ephemera = provideEphemera(state.idInManager);
|
|
503
|
-
const { outerUpdater: updaterPre } = ephemera;
|
|
504
|
-
const { vaultSeat } = state;
|
|
505
|
-
const proposal = clientSeat.getProposal();
|
|
506
|
-
assertOnlyKeys(proposal, ['Collateral', 'Minted']);
|
|
507
|
-
|
|
508
|
-
const allEmpty = amounts => {
|
|
509
|
-
return amounts.every(a => AmountMath.isEmpty(a));
|
|
510
|
-
};
|
|
511
|
-
|
|
512
|
-
const normalizedDebtPre = self.getNormalizedDebt();
|
|
513
|
-
const collateralPre = helper.getCollateralAllocated(vaultSeat);
|
|
514
|
-
|
|
515
|
-
const giveColl = proposal.give.Collateral || helper.emptyCollateral();
|
|
516
|
-
const wantColl = proposal.want.Collateral || helper.emptyCollateral();
|
|
517
|
-
|
|
518
|
-
const newCollateralPre = addSubtract(collateralPre, giveColl, wantColl);
|
|
519
|
-
// max debt supported by current Collateral as modified by proposal
|
|
520
|
-
const maxDebtPre = await state.manager.maxDebtFor(newCollateralPre);
|
|
521
|
-
updaterPre === ephemera.outerUpdater ||
|
|
522
|
-
Fail`Transfer during vault adjustment`;
|
|
523
|
-
helper.assertActive();
|
|
524
|
-
|
|
525
|
-
// After the `await`, we retrieve the vault's allocations again,
|
|
526
|
-
// so we can compare to the debt limit based on the new values.
|
|
527
|
-
const collateral = helper.getCollateralAllocated(vaultSeat);
|
|
528
|
-
const newCollateral = addSubtract(collateral, giveColl, wantColl);
|
|
529
|
-
|
|
530
|
-
const debt = self.getCurrentDebt();
|
|
531
|
-
const giveMinted = AmountMath.min(
|
|
532
|
-
proposal.give.Minted || helper.emptyDebt(),
|
|
533
|
-
debt,
|
|
534
|
-
);
|
|
535
|
-
const wantMinted = proposal.want.Minted || helper.emptyDebt();
|
|
536
|
-
if (allEmpty([giveColl, giveMinted, wantColl, wantMinted])) {
|
|
601
|
+
helper.updateUiState();
|
|
537
602
|
clientSeat.exit();
|
|
538
|
-
return '
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
stageDelta(clientSeat, vaultSeat, giveColl, wantColl, 'Collateral');
|
|
565
|
-
// `wantMinted` is allocated in the reallocate and mint operation, and so not here
|
|
566
|
-
stageDelta(
|
|
567
|
-
clientSeat,
|
|
568
|
-
vaultSeat,
|
|
569
|
-
giveMinted,
|
|
570
|
-
helper.emptyDebt(),
|
|
571
|
-
'Minted',
|
|
572
|
-
);
|
|
573
|
-
|
|
574
|
-
/** @type {Array<ZCFSeat>} */
|
|
575
|
-
const vaultSeatOpt = allEmpty([giveColl, giveMinted, wantColl])
|
|
576
|
-
? []
|
|
577
|
-
: [vaultSeat];
|
|
578
|
-
state.manager.mintAndReallocate(
|
|
579
|
-
toMint,
|
|
580
|
-
fee,
|
|
581
|
-
clientSeat,
|
|
582
|
-
...vaultSeatOpt,
|
|
583
|
-
);
|
|
584
|
-
|
|
585
|
-
// parent needs to know about the change in debt
|
|
586
|
-
helper.updateDebtAccounting(normalizedDebtPre, collateralPre, newDebt);
|
|
587
|
-
state.manager.burnAndRecord(giveMinted, vaultSeat);
|
|
588
|
-
helper.assertVaultHoldsNoMinted();
|
|
589
|
-
|
|
590
|
-
helper.updateUiState();
|
|
591
|
-
clientSeat.exit();
|
|
592
|
-
return 'We have adjusted your balances, thank you for your business';
|
|
593
|
-
},
|
|
594
|
-
|
|
595
|
-
/**
|
|
596
|
-
*
|
|
597
|
-
* @param {ZCFSeat} seat
|
|
598
|
-
* @returns {VaultKit}
|
|
599
|
-
*/
|
|
600
|
-
makeTransferInvitationHook(seat) {
|
|
601
|
-
const { state, facets } = this;
|
|
602
|
-
|
|
603
|
-
const { self, helper } = facets;
|
|
604
|
-
helper.assertCloseable();
|
|
605
|
-
seat.exit();
|
|
606
|
-
|
|
607
|
-
const ephemera = provideEphemera(state.idInManager);
|
|
608
|
-
|
|
609
|
-
// eslint-disable-next-line no-use-before-define
|
|
610
|
-
const vaultKit = makeVaultKit(
|
|
611
|
-
self,
|
|
612
|
-
ephemera.storageNode,
|
|
613
|
-
ephemera.marshaller,
|
|
614
|
-
state.manager.getAssetSubscriber(),
|
|
615
|
-
);
|
|
616
|
-
ephemera.outerUpdater = vaultKit.vaultUpdater;
|
|
617
|
-
helper.updateUiState();
|
|
618
|
-
|
|
619
|
-
return vaultKit;
|
|
620
|
-
},
|
|
621
|
-
},
|
|
622
|
-
self: {
|
|
623
|
-
getVaultSeat() {
|
|
624
|
-
return this.state.vaultSeat;
|
|
625
|
-
},
|
|
626
|
-
|
|
627
|
-
/**
|
|
628
|
-
* @param {ZCFSeat} seat
|
|
629
|
-
* @param {ERef<StorageNode>} storageNode
|
|
630
|
-
*/
|
|
631
|
-
async initVaultKit(seat, storageNode) {
|
|
632
|
-
const { state, facets } = this;
|
|
633
|
-
|
|
634
|
-
const ephemera = provideEphemera(state.idInManager);
|
|
635
|
-
|
|
636
|
-
const { self, helper } = facets;
|
|
637
|
-
|
|
638
|
-
const normalizedDebtPre = self.getNormalizedDebt();
|
|
639
|
-
const actualDebtPre = self.getCurrentDebt();
|
|
640
|
-
(AmountMath.isEmpty(normalizedDebtPre) &&
|
|
641
|
-
AmountMath.isEmpty(actualDebtPre)) ||
|
|
642
|
-
Fail`vault must be empty initially`;
|
|
643
|
-
|
|
644
|
-
const collateralPre = self.getCollateralAmount();
|
|
645
|
-
trace('initVaultKit start: collateral', state.idInManager, {
|
|
646
|
-
actualDebtPre,
|
|
647
|
-
collateralPre,
|
|
648
|
-
});
|
|
649
|
-
|
|
650
|
-
// get the payout to provide access to the collateral if the
|
|
651
|
-
// contract abandons
|
|
652
|
-
const {
|
|
653
|
-
give: { Collateral: giveCollateral },
|
|
654
|
-
want: { Minted: wantMinted },
|
|
655
|
-
} = seat.getProposal();
|
|
656
|
-
|
|
657
|
-
const {
|
|
658
|
-
newDebt: newDebtPre,
|
|
659
|
-
fee,
|
|
660
|
-
toMint,
|
|
661
|
-
} = helper.loanFee(actualDebtPre, helper.emptyDebt(), wantMinted);
|
|
662
|
-
!AmountMath.isEmpty(fee) ||
|
|
663
|
-
Fail`loan requested (${wantMinted}) is too small; cannot accrue interest`;
|
|
664
|
-
AmountMath.isEqual(newDebtPre, toMint) || Fail`fee mismatch for vault`;
|
|
665
|
-
trace(
|
|
666
|
-
'initVault',
|
|
667
|
-
state.idInManager,
|
|
668
|
-
{ wantedRun: wantMinted, fee },
|
|
669
|
-
self.getCollateralAmount(),
|
|
670
|
-
);
|
|
671
|
-
|
|
672
|
-
await helper.assertSufficientCollateral(giveCollateral, newDebtPre);
|
|
673
|
-
|
|
674
|
-
const { vaultSeat } = state;
|
|
675
|
-
vaultSeat.incrementBy(
|
|
676
|
-
seat.decrementBy(harden({ Collateral: giveCollateral })),
|
|
677
|
-
);
|
|
678
|
-
state.manager.mintAndReallocate(toMint, fee, seat, vaultSeat);
|
|
679
|
-
helper.updateDebtAccounting(
|
|
680
|
-
normalizedDebtPre,
|
|
681
|
-
collateralPre,
|
|
682
|
-
newDebtPre,
|
|
683
|
-
);
|
|
684
|
-
|
|
685
|
-
const vaultKit = makeVaultKit(
|
|
686
|
-
self,
|
|
687
|
-
storageNode,
|
|
688
|
-
ephemera.marshaller,
|
|
689
|
-
state.manager.getAssetSubscriber(),
|
|
690
|
-
);
|
|
691
|
-
ephemera.outerUpdater = vaultKit.vaultUpdater;
|
|
692
|
-
helper.updateUiState();
|
|
693
|
-
return vaultKit;
|
|
694
|
-
},
|
|
695
|
-
|
|
696
|
-
/**
|
|
697
|
-
* Called by manager at start of liquidation.
|
|
698
|
-
*/
|
|
699
|
-
liquidating() {
|
|
700
|
-
const { facets } = this;
|
|
701
|
-
|
|
702
|
-
const { helper } = facets;
|
|
703
|
-
helper.assignPhase(Phase.LIQUIDATING);
|
|
704
|
-
helper.updateUiState();
|
|
705
|
-
},
|
|
603
|
+
return 'We have adjusted your balances, thank you for your business';
|
|
604
|
+
},
|
|
605
|
+
|
|
606
|
+
/**
|
|
607
|
+
*
|
|
608
|
+
* @param {ZCFSeat} seat
|
|
609
|
+
* @returns {VaultKit}
|
|
610
|
+
*/
|
|
611
|
+
makeTransferInvitationHook(seat) {
|
|
612
|
+
const { state, facets } = this;
|
|
613
|
+
|
|
614
|
+
const { self, helper } = facets;
|
|
615
|
+
helper.assertCloseable();
|
|
616
|
+
seat.exit();
|
|
617
|
+
|
|
618
|
+
const ephemera = provideEphemera(state.idInManager);
|
|
619
|
+
|
|
620
|
+
// eslint-disable-next-line no-use-before-define
|
|
621
|
+
const vaultKit = makeVaultKit(
|
|
622
|
+
self,
|
|
623
|
+
ephemera.storageNode,
|
|
624
|
+
ephemera.marshaller,
|
|
625
|
+
state.manager.getAssetSubscriber(),
|
|
626
|
+
);
|
|
627
|
+
ephemera.outerUpdater = vaultKit.vaultUpdater;
|
|
628
|
+
helper.updateUiState();
|
|
706
629
|
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
* covered.
|
|
710
|
-
*/
|
|
711
|
-
liquidated() {
|
|
712
|
-
const { facets } = this;
|
|
713
|
-
|
|
714
|
-
const { helper } = facets;
|
|
715
|
-
helper.updateDebtSnapshot(
|
|
716
|
-
// liquidated vaults retain no debt
|
|
717
|
-
AmountMath.makeEmpty(helper.debtBrand()),
|
|
718
|
-
);
|
|
719
|
-
|
|
720
|
-
helper.assignPhase(Phase.LIQUIDATED);
|
|
721
|
-
helper.updateUiState();
|
|
630
|
+
return vaultKit;
|
|
631
|
+
},
|
|
722
632
|
},
|
|
633
|
+
self: {
|
|
634
|
+
getVaultSeat() {
|
|
635
|
+
return this.state.vaultSeat;
|
|
636
|
+
},
|
|
637
|
+
|
|
638
|
+
/**
|
|
639
|
+
* @param {ZCFSeat} seat
|
|
640
|
+
* @param {ERef<StorageNode>} storageNode
|
|
641
|
+
*/
|
|
642
|
+
async initVaultKit(seat, storageNode) {
|
|
643
|
+
const { state, facets } = this;
|
|
644
|
+
|
|
645
|
+
const ephemera = provideEphemera(state.idInManager);
|
|
646
|
+
|
|
647
|
+
const { self, helper } = facets;
|
|
648
|
+
|
|
649
|
+
const normalizedDebtPre = self.getNormalizedDebt();
|
|
650
|
+
const actualDebtPre = self.getCurrentDebt();
|
|
651
|
+
(AmountMath.isEmpty(normalizedDebtPre) &&
|
|
652
|
+
AmountMath.isEmpty(actualDebtPre)) ||
|
|
653
|
+
Fail`vault must be empty initially`;
|
|
654
|
+
|
|
655
|
+
const collateralPre = self.getCollateralAmount();
|
|
656
|
+
trace('initVaultKit start: collateral', state.idInManager, {
|
|
657
|
+
actualDebtPre,
|
|
658
|
+
collateralPre,
|
|
659
|
+
});
|
|
723
660
|
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
seat => helper.adjustBalancesHook(seat),
|
|
731
|
-
'AdjustBalances',
|
|
732
|
-
);
|
|
733
|
-
},
|
|
661
|
+
// get the payout to provide access to the collateral if the
|
|
662
|
+
// contract abandons
|
|
663
|
+
const {
|
|
664
|
+
give: { Collateral: giveCollateral },
|
|
665
|
+
want: { Minted: wantMinted },
|
|
666
|
+
} = seat.getProposal();
|
|
734
667
|
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
668
|
+
const {
|
|
669
|
+
newDebt: newDebtPre,
|
|
670
|
+
fee,
|
|
671
|
+
toMint,
|
|
672
|
+
} = helper.loanFee(actualDebtPre, helper.emptyDebt(), wantMinted);
|
|
673
|
+
!AmountMath.isEmpty(fee) ||
|
|
674
|
+
Fail`loan requested (${wantMinted}) is too small; cannot accrue interest`;
|
|
675
|
+
AmountMath.isEqual(newDebtPre, toMint) ||
|
|
676
|
+
Fail`fee mismatch for vault`;
|
|
677
|
+
trace(
|
|
678
|
+
'initVault',
|
|
679
|
+
state.idInManager,
|
|
680
|
+
{ wantedRun: wantMinted, fee },
|
|
681
|
+
self.getCollateralAmount(),
|
|
682
|
+
);
|
|
742
683
|
|
|
743
|
-
|
|
744
|
-
* @returns {Promise<Invitation>}
|
|
745
|
-
*/
|
|
746
|
-
makeTransferInvitation() {
|
|
747
|
-
const { state, facets } = this;
|
|
748
|
-
const ephemera = provideEphemera(state.idInManager);
|
|
749
|
-
const { outerUpdater } = ephemera;
|
|
750
|
-
const { self, helper } = facets;
|
|
751
|
-
// Bring the debt snapshot current for the final report before transfer
|
|
752
|
-
helper.updateDebtSnapshot(self.getCurrentDebt());
|
|
753
|
-
const { debtSnapshot: debt, interestSnapshot: interest, phase } = state;
|
|
754
|
-
if (outerUpdater) {
|
|
755
|
-
outerUpdater.finish(helper.getStateSnapshot(Phase.TRANSFER));
|
|
756
|
-
ephemera.outerUpdater = null;
|
|
757
|
-
}
|
|
758
|
-
const transferState = {
|
|
759
|
-
debtSnapshot: { debt, interest },
|
|
760
|
-
locked: self.getCollateralAmount(),
|
|
761
|
-
vaultState: phase,
|
|
762
|
-
};
|
|
763
|
-
const { zcf } = provideEphemera(state.idInManager);
|
|
764
|
-
return zcf.makeInvitation(
|
|
765
|
-
seat => helper.makeTransferInvitationHook(seat),
|
|
766
|
-
'TransferVault',
|
|
767
|
-
transferState,
|
|
768
|
-
);
|
|
769
|
-
},
|
|
684
|
+
await helper.assertSufficientCollateral(giveCollateral, newDebtPre);
|
|
770
685
|
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
// getCollateralAllocated would return final allocations
|
|
782
|
-
return vaultSeat.hasExited()
|
|
783
|
-
? helper.emptyCollateral()
|
|
784
|
-
: helper.getCollateralAllocated(vaultSeat);
|
|
785
|
-
},
|
|
686
|
+
const { vaultSeat } = state;
|
|
687
|
+
vaultSeat.incrementBy(
|
|
688
|
+
seat.decrementBy(harden({ Collateral: giveCollateral })),
|
|
689
|
+
);
|
|
690
|
+
state.manager.mintAndReallocate(toMint, fee, seat, vaultSeat);
|
|
691
|
+
helper.updateDebtAccounting(
|
|
692
|
+
normalizedDebtPre,
|
|
693
|
+
collateralPre,
|
|
694
|
+
newDebtPre,
|
|
695
|
+
);
|
|
786
696
|
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
697
|
+
const vaultKit = makeVaultKit(
|
|
698
|
+
self,
|
|
699
|
+
storageNode,
|
|
700
|
+
ephemera.marshaller,
|
|
701
|
+
state.manager.getAssetSubscriber(),
|
|
702
|
+
);
|
|
703
|
+
ephemera.outerUpdater = vaultKit.vaultUpdater;
|
|
704
|
+
helper.updateUiState();
|
|
705
|
+
return vaultKit;
|
|
706
|
+
},
|
|
707
|
+
|
|
708
|
+
/**
|
|
709
|
+
* Called by manager at start of liquidation.
|
|
710
|
+
*/
|
|
711
|
+
liquidating() {
|
|
712
|
+
const { facets } = this;
|
|
713
|
+
|
|
714
|
+
const { helper } = facets;
|
|
715
|
+
helper.assignPhase(Phase.LIQUIDATING);
|
|
716
|
+
helper.updateUiState();
|
|
717
|
+
},
|
|
718
|
+
|
|
719
|
+
/**
|
|
720
|
+
* Called by manager at end of liquidation, at which point all debts have been
|
|
721
|
+
* covered.
|
|
722
|
+
*/
|
|
723
|
+
liquidated() {
|
|
724
|
+
const { facets } = this;
|
|
725
|
+
|
|
726
|
+
const { helper } = facets;
|
|
727
|
+
helper.updateDebtSnapshot(
|
|
728
|
+
// liquidated vaults retain no debt
|
|
729
|
+
AmountMath.makeEmpty(helper.debtBrand()),
|
|
730
|
+
);
|
|
808
731
|
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
732
|
+
helper.assignPhase(Phase.LIQUIDATED);
|
|
733
|
+
helper.updateUiState();
|
|
734
|
+
},
|
|
735
|
+
|
|
736
|
+
makeAdjustBalancesInvitation() {
|
|
737
|
+
const { state, facets } = this;
|
|
738
|
+
const { helper } = facets;
|
|
739
|
+
helper.assertActive();
|
|
740
|
+
const { zcf } = provideEphemera(state.idInManager);
|
|
741
|
+
return zcf.makeInvitation(
|
|
742
|
+
seat => helper.adjustBalancesHook(seat),
|
|
743
|
+
'AdjustBalances',
|
|
744
|
+
);
|
|
745
|
+
},
|
|
746
|
+
|
|
747
|
+
makeCloseInvitation() {
|
|
748
|
+
const { state, facets } = this;
|
|
749
|
+
const { helper } = facets;
|
|
750
|
+
helper.assertCloseable();
|
|
751
|
+
const { zcf } = provideEphemera(state.idInManager);
|
|
752
|
+
return zcf.makeInvitation(
|
|
753
|
+
seat => helper.closeHook(seat),
|
|
754
|
+
'CloseVault',
|
|
755
|
+
);
|
|
756
|
+
},
|
|
757
|
+
|
|
758
|
+
/**
|
|
759
|
+
* @returns {Promise<Invitation>}
|
|
760
|
+
*/
|
|
761
|
+
makeTransferInvitation() {
|
|
762
|
+
const { state, facets } = this;
|
|
763
|
+
const ephemera = provideEphemera(state.idInManager);
|
|
764
|
+
const { outerUpdater } = ephemera;
|
|
765
|
+
const { self, helper } = facets;
|
|
766
|
+
// Bring the debt snapshot current for the final report before transfer
|
|
767
|
+
helper.updateDebtSnapshot(self.getCurrentDebt());
|
|
768
|
+
const {
|
|
769
|
+
debtSnapshot: debt,
|
|
770
|
+
interestSnapshot: interest,
|
|
771
|
+
phase,
|
|
772
|
+
} = state;
|
|
773
|
+
if (outerUpdater) {
|
|
774
|
+
outerUpdater.finish(helper.getStateSnapshot(Phase.TRANSFER));
|
|
775
|
+
ephemera.outerUpdater = null;
|
|
776
|
+
}
|
|
777
|
+
const transferState = {
|
|
778
|
+
debtSnapshot: { debt, interest },
|
|
779
|
+
locked: self.getCollateralAmount(),
|
|
780
|
+
vaultState: phase,
|
|
781
|
+
};
|
|
782
|
+
const { zcf } = provideEphemera(state.idInManager);
|
|
783
|
+
return zcf.makeInvitation(
|
|
784
|
+
seat => helper.makeTransferInvitationHook(seat),
|
|
785
|
+
'TransferVault',
|
|
786
|
+
transferState,
|
|
787
|
+
);
|
|
788
|
+
},
|
|
789
|
+
|
|
790
|
+
// for status/debugging
|
|
791
|
+
|
|
792
|
+
/**
|
|
793
|
+
*
|
|
794
|
+
* @returns {Amount<'nat'>}
|
|
795
|
+
*/
|
|
796
|
+
getCollateralAmount() {
|
|
797
|
+
const { state, facets } = this;
|
|
798
|
+
const { vaultSeat } = state;
|
|
799
|
+
const { helper } = facets;
|
|
800
|
+
// getCollateralAllocated would return final allocations
|
|
801
|
+
return vaultSeat.hasExited()
|
|
802
|
+
? helper.emptyCollateral()
|
|
803
|
+
: helper.getCollateralAllocated(vaultSeat);
|
|
804
|
+
},
|
|
805
|
+
|
|
806
|
+
/**
|
|
807
|
+
* The actual current debt, including accrued interest.
|
|
808
|
+
*
|
|
809
|
+
* This looks like a simple getter but it does a lot of the heavy lifting for
|
|
810
|
+
* interest accrual. Rather than updating all records when interest accrues,
|
|
811
|
+
* the vault manager updates just its rolling compounded interest. Here we
|
|
812
|
+
* calculate what the current debt is given what's recorded in this vault and
|
|
813
|
+
* what interest has compounded since this vault record was written.
|
|
814
|
+
*
|
|
815
|
+
* @see getNormalizedDebt
|
|
816
|
+
*
|
|
817
|
+
* @returns {Amount<'nat'>}
|
|
818
|
+
*/
|
|
819
|
+
getCurrentDebt() {
|
|
820
|
+
const { state } = this;
|
|
821
|
+
return calculateCurrentDebt(
|
|
822
|
+
state.debtSnapshot,
|
|
823
|
+
state.interestSnapshot,
|
|
824
|
+
state.manager.getCompoundedInterest(),
|
|
825
|
+
);
|
|
826
|
+
},
|
|
827
|
+
|
|
828
|
+
/**
|
|
829
|
+
* The normalization puts all debts on a common time-independent scale since
|
|
830
|
+
* the launch of this vault manager. This allows the manager to order vaults
|
|
831
|
+
* by their debt-to-collateral ratios without having to mutate the debts as
|
|
832
|
+
* the interest accrues.
|
|
833
|
+
*
|
|
834
|
+
* @see getActualDebAmount
|
|
835
|
+
*
|
|
836
|
+
* @returns {import('./storeUtils.js').NormalizedDebt} as if the vault was open at the launch of this manager, before any interest accrued
|
|
837
|
+
*/
|
|
838
|
+
getNormalizedDebt() {
|
|
839
|
+
const { state } = this;
|
|
840
|
+
// @ts-expect-error cast
|
|
841
|
+
return reverseInterest(state.debtSnapshot, state.interestSnapshot);
|
|
842
|
+
},
|
|
823
843
|
},
|
|
824
844
|
},
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
/**
|
|
829
|
-
* @param {ZCF} zcf
|
|
830
|
-
* @param {VaultManager} manager
|
|
831
|
-
* @param {VaultId} idInManager
|
|
832
|
-
*/
|
|
833
|
-
export const makeVault = pickFacet(makeVaultBase, 'self');
|
|
845
|
+
);
|
|
846
|
+
return maker;
|
|
847
|
+
};
|
|
834
848
|
|
|
835
|
-
/** @typedef {ReturnType<typeof
|
|
849
|
+
/** @typedef {ReturnType<ReturnType<typeof vivifyVault>>['self']} Vault */
|