@agoric/inter-protocol 0.15.0 → 0.16.1-dev-51ee28f.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/CHANGELOG.md +45 -0
- package/bundles/bundle-auctioneer-js-meta.json +284 -284
- package/bundles/bundle-auctioneer.js +1 -1
- package/bundles/bundle-econCommitteeCharter-js-meta.json +191 -191
- package/bundles/bundle-econCommitteeCharter.js +1 -1
- package/bundles/bundle-feeDistributor-js-meta.json +189 -189
- package/bundles/bundle-feeDistributor.js +1 -1
- package/bundles/bundle-fluxAggregatorKit-js-meta.json +283 -283
- package/bundles/bundle-fluxAggregatorKit.js +1 -1
- package/bundles/bundle-psm-js-meta.json +277 -277
- package/bundles/bundle-psm.js +1 -1
- package/bundles/bundle-reserve-js-meta.json +298 -298
- package/bundles/bundle-reserve.js +1 -1
- package/bundles/bundle-scaledPriceAuthority-js-meta.json +166 -166
- package/bundles/bundle-scaledPriceAuthority.js +1 -1
- package/bundles/bundle-vaultFactory-js-meta.json +354 -350
- package/bundles/bundle-vaultFactory.js +1 -1
- package/package.json +17 -17
- package/scripts/init-core.js +2 -2
- package/scripts/start-local-chain.sh +1 -1
- package/src/auction/auctionBook.js +10 -2
- package/src/clientSupport.js +79 -7
- package/src/contractSupport.js +16 -45
- package/src/price/fluxAggregatorContract.js +1 -1
- package/src/price/fluxAggregatorKit.js +2 -15
- package/src/price/roundsManager.js +8 -0
- package/src/proposals/core-proposal.js +4 -4
- package/src/proposals/econ-behaviors.js +13 -12
- package/src/reserve/assetReserveKit.js +1 -7
- package/src/reserve/doc.md +4 -4
- package/src/vaultFactory/Liquidation.md +1 -1
- package/src/vaultFactory/liquidation.js +14 -24
- package/src/vaultFactory/params.js +8 -8
- package/src/vaultFactory/proceeds.js +274 -0
- package/src/vaultFactory/types.js +1 -10
- package/src/vaultFactory/vaultDirector.js +3 -42
- package/src/vaultFactory/vaultFactory.js +3 -3
- package/src/vaultFactory/vaultHolder.js +1 -9
- package/src/vaultFactory/vaultManager.js +177 -322
|
@@ -37,17 +37,13 @@ import {
|
|
|
37
37
|
import { TransferPartShape } from '@agoric/zoe/src/contractSupport/atomicTransfer.js';
|
|
38
38
|
import {
|
|
39
39
|
atomicRearrange,
|
|
40
|
-
ceilDivideBy,
|
|
41
40
|
ceilMultiplyBy,
|
|
42
41
|
floorDivideBy,
|
|
43
|
-
floorMultiplyBy,
|
|
44
42
|
getAmountIn,
|
|
45
43
|
getAmountOut,
|
|
46
44
|
makeEphemeraProvider,
|
|
47
45
|
makeRatio,
|
|
48
|
-
makeRatioFromAmounts,
|
|
49
46
|
makeRecorderTopic,
|
|
50
|
-
multiplyRatios,
|
|
51
47
|
offerTo,
|
|
52
48
|
SubscriberShape,
|
|
53
49
|
TopicsRecordShape,
|
|
@@ -55,14 +51,19 @@ import {
|
|
|
55
51
|
import { PriceQuoteShape, SeatShape } from '@agoric/zoe/src/typeGuards.js';
|
|
56
52
|
import { E } from '@endo/eventual-send';
|
|
57
53
|
import { AuctionPFShape } from '../auction/auctioneer.js';
|
|
58
|
-
import {
|
|
54
|
+
import {
|
|
55
|
+
checkDebtLimit,
|
|
56
|
+
makeNatAmountShape,
|
|
57
|
+
quoteAsRatio,
|
|
58
|
+
} from '../contractSupport.js';
|
|
59
59
|
import { chargeInterest } from '../interest.js';
|
|
60
|
-
import { getLiquidatableVaults
|
|
60
|
+
import { getLiquidatableVaults } from './liquidation.js';
|
|
61
61
|
import { calculateMinimumCollateralization, minimumPrice } from './math.js';
|
|
62
62
|
import { makePrioritizedVaults } from './prioritizedVaults.js';
|
|
63
63
|
import { Phase, prepareVault } from './vault.js';
|
|
64
|
+
import { calculateDistributionPlan } from './proceeds.js';
|
|
64
65
|
|
|
65
|
-
const { details: X, Fail } = assert;
|
|
66
|
+
const { details: X, Fail, quote: q } = assert;
|
|
66
67
|
|
|
67
68
|
const trace = makeTracer('VM');
|
|
68
69
|
|
|
@@ -73,6 +74,8 @@ const trace = makeTracer('VM');
|
|
|
73
74
|
/**
|
|
74
75
|
* @typedef {object} MetricsNotification
|
|
75
76
|
*
|
|
77
|
+
* @property {Ratio | null} lockedQuote priceQuote that will be used for liquidation.
|
|
78
|
+
* Non-null from priceLock time until liquidation has taken place.
|
|
76
79
|
* @property {number} numActiveVaults present count of vaults
|
|
77
80
|
* @property {number} numLiquidatingVaults present count of liquidating vaults
|
|
78
81
|
* @property {Amount<'nat'>} totalCollateral present sum of collateral across all vaults
|
|
@@ -152,10 +155,6 @@ const trace = makeTracer('VM');
|
|
|
152
155
|
* }} MutableState
|
|
153
156
|
*/
|
|
154
157
|
|
|
155
|
-
/** @param {Pick<PriceDescription, 'amountIn' | 'amountOut'>} quoteAmount */
|
|
156
|
-
const quoteAsRatio = quoteAmount =>
|
|
157
|
-
makeRatioFromAmounts(quoteAmount.amountIn, quoteAmount.amountOut);
|
|
158
|
-
|
|
159
158
|
/**
|
|
160
159
|
* @type {(brand: Brand) => {
|
|
161
160
|
* prioritizedVaults: ReturnType<typeof makePrioritizedVaults>,
|
|
@@ -254,8 +253,6 @@ export const prepareVaultManagerKit = (
|
|
|
254
253
|
{
|
|
255
254
|
collateral: M.interface('collateral', {
|
|
256
255
|
makeVaultInvitation: M.call().returns(M.promise()),
|
|
257
|
-
getSubscriber: M.call().returns(SubscriberShape),
|
|
258
|
-
getMetrics: M.call().returns(SubscriberShape),
|
|
259
256
|
getPublicTopics: M.call().returns(TopicsRecordShape),
|
|
260
257
|
getQuotes: M.call().returns(NotifierShape),
|
|
261
258
|
getCompoundedInterest: M.call().returns(RatioShape),
|
|
@@ -318,14 +315,6 @@ export const prepareVaultManagerKit = (
|
|
|
318
315
|
}),
|
|
319
316
|
);
|
|
320
317
|
},
|
|
321
|
-
/** @deprecated use getPublicTopics */
|
|
322
|
-
getSubscriber() {
|
|
323
|
-
return this.state.assetTopicKit.subscriber;
|
|
324
|
-
},
|
|
325
|
-
/** @deprecated use getPublicTopics */
|
|
326
|
-
getMetrics() {
|
|
327
|
-
return this.state.metricsTopicKit.subscriber;
|
|
328
|
-
},
|
|
329
318
|
getQuotes() {
|
|
330
319
|
const ephemera = collateralEphemera(this.state.collateralBrand);
|
|
331
320
|
return ephemera.storedQuotesNotifier;
|
|
@@ -499,30 +488,22 @@ export const prepareVaultManagerKit = (
|
|
|
499
488
|
sendToReserve(penalty, seat, seatKeyword = 'Collateral') {
|
|
500
489
|
const invitation =
|
|
501
490
|
E(reservePublicFacet).makeAddCollateralInvitation();
|
|
491
|
+
trace('Sending to reserve: ', penalty);
|
|
502
492
|
|
|
503
493
|
// don't wait for response
|
|
504
494
|
void E.when(invitation, invite => {
|
|
505
495
|
const proposal = { give: { Collateral: penalty } };
|
|
506
|
-
|
|
496
|
+
return offerTo(
|
|
507
497
|
zcf,
|
|
508
498
|
invite,
|
|
509
499
|
{ [seatKeyword]: 'Collateral' },
|
|
510
500
|
proposal,
|
|
511
501
|
seat,
|
|
512
502
|
);
|
|
503
|
+
}).catch(reason => {
|
|
504
|
+
console.error('sendToReserve failed', reason);
|
|
513
505
|
});
|
|
514
506
|
},
|
|
515
|
-
/** @type {(collatSold: Amount<'nat'>, completed: number, aborted?: number) => void} */
|
|
516
|
-
markCollateralLiquidated(collatSold, completed, aborted = 0) {
|
|
517
|
-
const { state } = this;
|
|
518
|
-
|
|
519
|
-
state.totalCollateral = AmountMath.subtract(
|
|
520
|
-
state.totalCollateral,
|
|
521
|
-
collatSold,
|
|
522
|
-
);
|
|
523
|
-
state.numLiquidationsCompleted += completed;
|
|
524
|
-
state.numLiquidationsAborted += aborted;
|
|
525
|
-
},
|
|
526
507
|
markLiquidating(debt, collateral) {
|
|
527
508
|
const { state } = this;
|
|
528
509
|
|
|
@@ -537,9 +518,10 @@ export const prepareVaultManagerKit = (
|
|
|
537
518
|
*
|
|
538
519
|
* @param {Amount<'nat'>} debt
|
|
539
520
|
* @param {Amount<'nat'>} collateral
|
|
540
|
-
* @param {
|
|
521
|
+
* @param {Amount<'nat'>} overage
|
|
522
|
+
* @param {Amount<'nat'>} shortfall
|
|
541
523
|
*/
|
|
542
|
-
markDoneLiquidating(debt, collateral,
|
|
524
|
+
markDoneLiquidating(debt, collateral, overage, shortfall) {
|
|
543
525
|
const { state } = this;
|
|
544
526
|
|
|
545
527
|
// update liquidation state
|
|
@@ -555,7 +537,6 @@ export const prepareVaultManagerKit = (
|
|
|
555
537
|
|
|
556
538
|
// record shortfall and proceeds
|
|
557
539
|
|
|
558
|
-
const { overage, shortfall } = accounting;
|
|
559
540
|
// cumulative values
|
|
560
541
|
state.totalOverageReceived = AmountMath.add(
|
|
561
542
|
state.totalOverageReceived,
|
|
@@ -579,25 +560,6 @@ export const prepareVaultManagerKit = (
|
|
|
579
560
|
console.error('🚨 failed to report liquidation shortfall', err);
|
|
580
561
|
});
|
|
581
562
|
},
|
|
582
|
-
/**
|
|
583
|
-
* If interest was charged between liquidating and liquidated, erase it.
|
|
584
|
-
*
|
|
585
|
-
* @param {Amount<'nat'>} priorDebtAmount
|
|
586
|
-
* @param {Amount<'nat'>} currentDebt
|
|
587
|
-
*/
|
|
588
|
-
subtractPhantomInterest(priorDebtAmount, currentDebt) {
|
|
589
|
-
const { state } = this;
|
|
590
|
-
const difference = AmountMath.subtract(currentDebt, priorDebtAmount);
|
|
591
|
-
|
|
592
|
-
if (!AmountMath.isEmpty(difference)) {
|
|
593
|
-
state.totalDebt = AmountMath.subtract(state.totalDebt, difference);
|
|
594
|
-
}
|
|
595
|
-
},
|
|
596
|
-
markRestoreDebt(debt) {
|
|
597
|
-
const { state } = this;
|
|
598
|
-
|
|
599
|
-
state.totalDebt = AmountMath.add(state.totalDebt, debt);
|
|
600
|
-
},
|
|
601
563
|
writeMetrics() {
|
|
602
564
|
const { state } = this;
|
|
603
565
|
const { collateralBrand, retainedCollateralSeat, metricsTopicKit } =
|
|
@@ -608,6 +570,11 @@ export const prepareVaultManagerKit = (
|
|
|
608
570
|
retainedCollateralSeat.getCurrentAllocation()?.Collateral ??
|
|
609
571
|
AmountMath.makeEmpty(collateralBrand, 'nat');
|
|
610
572
|
|
|
573
|
+
const quote = state.lockedQuote;
|
|
574
|
+
const lockedQuoteRatio = quote
|
|
575
|
+
? quoteAsRatio(quote.quoteAmount.value[0])
|
|
576
|
+
: null;
|
|
577
|
+
|
|
611
578
|
/** @type {MetricsNotification} */
|
|
612
579
|
const payload = harden({
|
|
613
580
|
numActiveVaults: prioritizedVaults.getCount(),
|
|
@@ -624,21 +591,31 @@ export const prepareVaultManagerKit = (
|
|
|
624
591
|
totalOverageReceived: state.totalOverageReceived,
|
|
625
592
|
totalProceedsReceived: state.totalProceedsReceived,
|
|
626
593
|
totalShortfallReceived: state.totalShortfallReceived,
|
|
594
|
+
lockedQuote: lockedQuoteRatio,
|
|
627
595
|
});
|
|
628
596
|
|
|
629
597
|
return E(metricsTopicKit.recorder).write(payload);
|
|
630
598
|
},
|
|
631
599
|
|
|
632
|
-
|
|
600
|
+
/**
|
|
601
|
+
* This is designed to tolerate an incomplete plan, in case calculateDistributionPlan encounters
|
|
602
|
+
* an error during its calculation. We don't have a way to induce such errors in CI so we've
|
|
603
|
+
* done so manually in dev and verified this function recovers as expected.
|
|
604
|
+
*
|
|
605
|
+
* @param {AmountKeywordRecord} proceeds
|
|
606
|
+
* @param {Amount<'nat'>} totalDebt
|
|
607
|
+
* @param {Pick<PriceQuote, 'quoteAmount'>} oraclePriceAtStart
|
|
608
|
+
* @param {MapStore<Vault, { collateralAmount: Amount<'nat'>, debtAmount: Amount<'nat'>}>} vaultData
|
|
609
|
+
* @param {Amount<'nat'>} totalCollateral
|
|
610
|
+
*/
|
|
611
|
+
planProceedsDistribution(
|
|
633
612
|
proceeds,
|
|
634
613
|
totalDebt,
|
|
635
614
|
oraclePriceAtStart,
|
|
636
|
-
liqSeat,
|
|
637
615
|
vaultData,
|
|
638
616
|
totalCollateral,
|
|
639
617
|
) {
|
|
640
618
|
const { state, facets } = this;
|
|
641
|
-
const { collateralBrand, debtBrand, liquidatingVaults } = this.state;
|
|
642
619
|
|
|
643
620
|
const { Collateral: collateralProceeds } = proceeds;
|
|
644
621
|
/** @type {Amount<'nat'>} */
|
|
@@ -651,280 +628,135 @@ export const prepareVaultManagerKit = (
|
|
|
651
628
|
collateralSold,
|
|
652
629
|
);
|
|
653
630
|
|
|
654
|
-
const mintedProceeds =
|
|
655
|
-
proceeds.Minted || AmountMath.makeEmpty(debtBrand);
|
|
656
|
-
const accounting = liquidationResults(totalDebt, mintedProceeds);
|
|
657
|
-
|
|
658
|
-
/** @type {Array<Vault>} */
|
|
659
|
-
const vaultsToLiquidate = [];
|
|
660
|
-
const liquidateAll = () => {
|
|
661
|
-
for (const vault of vaultsToLiquidate) {
|
|
662
|
-
vault.liquidated();
|
|
663
|
-
liquidatingVaults.delete(vault);
|
|
664
|
-
}
|
|
665
|
-
};
|
|
666
|
-
|
|
667
631
|
const penaltyRate = facets.self
|
|
668
632
|
.getGovernedParams()
|
|
669
633
|
.getLiquidationPenalty();
|
|
670
|
-
// charged in collateral
|
|
671
|
-
const totalPenalty = ceilMultiplyBy(
|
|
672
|
-
totalDebt,
|
|
673
|
-
multiplyRatios(
|
|
674
|
-
penaltyRate,
|
|
675
|
-
quoteAsRatio(oraclePriceAtStart.quoteAmount.value[0]),
|
|
676
|
-
),
|
|
677
|
-
);
|
|
678
|
-
|
|
679
|
-
const price = makeRatioFromAmounts(mintedProceeds, collateralSold);
|
|
680
|
-
// Liquidation.md describes how to process liquidation proceeds
|
|
681
634
|
const bestToWorst = [...vaultData.entries()].reverse();
|
|
682
|
-
if (AmountMath.isEmpty(accounting.shortfall)) {
|
|
683
|
-
// Flow #1: no shortfall
|
|
684
|
-
|
|
685
|
-
const collateralToDistribute = AmountMath.isGTE(
|
|
686
|
-
collateralProceeds,
|
|
687
|
-
totalPenalty,
|
|
688
|
-
);
|
|
689
|
-
|
|
690
|
-
const distributableCollateral = collateralToDistribute
|
|
691
|
-
? AmountMath.subtract(collateralProceeds, totalPenalty)
|
|
692
|
-
: AmountMath.makeEmptyFromAmount(collateralProceeds);
|
|
693
635
|
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
636
|
+
// unzip the entry tuples
|
|
637
|
+
const vaultsInPlan = /** @type {Vault[]} */ ([]);
|
|
638
|
+
const vaultsBalances =
|
|
639
|
+
/** @type {import('./proceeds.js').VaultBalances[]} */ ([]);
|
|
640
|
+
for (const [vault, balances] of bestToWorst) {
|
|
641
|
+
vaultsInPlan.push(vault);
|
|
642
|
+
vaultsBalances.push({
|
|
643
|
+
collateral: balances.collateralAmount,
|
|
644
|
+
// if interest accrued during sale, the current debt will be higher
|
|
645
|
+
presaleDebt: balances.debtAmount,
|
|
646
|
+
currentDebt: vault.getCurrentDebt(),
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
harden(vaultsInPlan);
|
|
650
|
+
harden(vaultsBalances);
|
|
698
651
|
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
652
|
+
const plan = calculateDistributionPlan({
|
|
653
|
+
proceeds,
|
|
654
|
+
totalDebt,
|
|
655
|
+
totalCollateral,
|
|
656
|
+
oraclePriceAtStart: oraclePriceAtStart.quoteAmount.value[0],
|
|
657
|
+
vaultsBalances,
|
|
658
|
+
penaltyRate,
|
|
659
|
+
});
|
|
660
|
+
return { plan, vaultsInPlan };
|
|
661
|
+
},
|
|
702
662
|
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
663
|
+
/**
|
|
664
|
+
* This is designed to tolerate an incomplete plan, in case calculateDistributionPlan encounters
|
|
665
|
+
* an error during its calculation. We don't have a way to induce such errors in CI so we've
|
|
666
|
+
* done so manually in dev and verified this function recovers as expected.
|
|
667
|
+
*
|
|
668
|
+
* @param {object} obj
|
|
669
|
+
* @param {import('./proceeds.js').DistributionPlan} obj.plan
|
|
670
|
+
* @param {Array<Vault>} obj.vaultsInPlan
|
|
671
|
+
* @param {ZCFSeat} obj.liqSeat
|
|
672
|
+
* @param {Amount<'nat'>} obj.totalCollateral
|
|
673
|
+
* @param {Amount<'nat'>} obj.totalDebt
|
|
674
|
+
* @returns {void}
|
|
675
|
+
*/
|
|
676
|
+
distributeProceeds({
|
|
677
|
+
plan,
|
|
678
|
+
vaultsInPlan,
|
|
679
|
+
liqSeat,
|
|
680
|
+
totalCollateral,
|
|
681
|
+
totalDebt,
|
|
682
|
+
}) {
|
|
683
|
+
const { state, facets } = this;
|
|
684
|
+
// Putting all the rearrangements after the loop ensures that errors
|
|
685
|
+
// in the calculations don't result in paying back some vaults and
|
|
686
|
+
// leaving others hanging.
|
|
687
|
+
if (plan.transfersToVault.length > 0) {
|
|
688
|
+
const transfers = plan.transfersToVault.map(
|
|
689
|
+
([vaultIndex, amounts]) =>
|
|
690
|
+
/** @type {import('@agoric/zoe/src/contractSupport/atomicTransfer.js').TransferPart} */ ([
|
|
719
691
|
liqSeat,
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
])
|
|
723
|
-
}
|
|
724
|
-
vaultsToLiquidate.push(vault);
|
|
725
|
-
}
|
|
726
|
-
if (transfers.length > 0) {
|
|
727
|
-
atomicRearrange(zcf, harden(transfers));
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
const forReserve = collateralToDistribute
|
|
731
|
-
? AmountMath.add(leftToStage, totalPenalty)
|
|
732
|
-
: collateralProceeds;
|
|
733
|
-
facets.helper.sendToReserve(forReserve, liqSeat);
|
|
734
|
-
|
|
735
|
-
facets.helper.markCollateralLiquidated(
|
|
736
|
-
totalCollateral,
|
|
737
|
-
vaultData.getSize(),
|
|
738
|
-
);
|
|
739
|
-
|
|
740
|
-
facets.helper.markDoneLiquidating(
|
|
741
|
-
totalDebt,
|
|
742
|
-
totalCollateral,
|
|
743
|
-
accounting,
|
|
744
|
-
);
|
|
745
|
-
} else if (AmountMath.isEmpty(collateralProceeds)) {
|
|
746
|
-
// Flow #2a
|
|
747
|
-
|
|
748
|
-
// charge penalty if proceeds are sufficient
|
|
749
|
-
const penaltyInMinted = ceilMultiplyBy(totalDebt, penaltyRate);
|
|
750
|
-
const recoveredDebt = AmountMath.min(
|
|
751
|
-
AmountMath.add(totalDebt, penaltyInMinted),
|
|
752
|
-
mintedProceeds,
|
|
753
|
-
);
|
|
754
|
-
|
|
755
|
-
facets.helper.burnToCoverDebt(
|
|
756
|
-
recoveredDebt,
|
|
757
|
-
mintedProceeds,
|
|
758
|
-
liqSeat,
|
|
692
|
+
vaultsInPlan[vaultIndex].getVaultSeat(),
|
|
693
|
+
amounts,
|
|
694
|
+
]),
|
|
759
695
|
);
|
|
696
|
+
atomicRearrange(zcf, harden(transfers));
|
|
697
|
+
}
|
|
760
698
|
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
699
|
+
const { prioritizedVaults } = collateralEphemera(
|
|
700
|
+
totalCollateral.brand,
|
|
701
|
+
);
|
|
702
|
+
state.numLiquidationsAborted += plan.vaultsToReinstate.length;
|
|
703
|
+
for (const vaultIndex of plan.vaultsToReinstate) {
|
|
704
|
+
const vault = vaultsInPlan[vaultIndex];
|
|
705
|
+
const vaultId = vault.abortLiquidation();
|
|
706
|
+
prioritizedVaults.addVault(vaultId, vault);
|
|
707
|
+
state.liquidatingVaults.delete(vault);
|
|
708
|
+
}
|
|
766
709
|
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
/** @type {import('@agoric/zoe/src/contractSupport/atomicTransfer.js').TransferPart[]} */
|
|
772
|
-
const transfers = [];
|
|
773
|
-
|
|
774
|
-
// iterate from best to worst returning remaining funds to vaults
|
|
775
|
-
/** @type {Array<[Vault, { collateralAmount: Amount<'nat'>, debtAmount: Amount<'nat'>}]>} */
|
|
776
|
-
for (const [vault, balance] of bestToWorst) {
|
|
777
|
-
// from best to worst, return minted above penalty if any remains
|
|
778
|
-
const { collateralAmount: vCollat, debtAmount } = balance;
|
|
779
|
-
const vaultShare = floorMultiplyBy(vCollat, vaultPortion);
|
|
780
|
-
facets.helper.subtractPhantomInterest(
|
|
781
|
-
debtAmount,
|
|
782
|
-
vault.getCurrentDebt(),
|
|
783
|
-
);
|
|
784
|
-
|
|
785
|
-
if (!AmountMath.isEmpty(mintedRemaining)) {
|
|
786
|
-
const mintedToReturn = AmountMath.isGTE(
|
|
787
|
-
mintedRemaining,
|
|
788
|
-
vaultShare,
|
|
789
|
-
)
|
|
790
|
-
? vaultShare
|
|
791
|
-
: mintedRemaining;
|
|
792
|
-
mintedRemaining = AmountMath.subtract(
|
|
793
|
-
mintedRemaining,
|
|
794
|
-
mintedToReturn,
|
|
795
|
-
);
|
|
796
|
-
const seat = vault.getVaultSeat();
|
|
797
|
-
transfers.push([liqSeat, seat, { Minted: mintedToReturn }]);
|
|
798
|
-
}
|
|
799
|
-
vaultsToLiquidate.push(vault);
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
if (transfers.length > 0) {
|
|
803
|
-
atomicRearrange(zcf, harden(transfers));
|
|
804
|
-
}
|
|
805
|
-
|
|
806
|
-
facets.helper.markCollateralLiquidated(
|
|
807
|
-
totalCollateral,
|
|
808
|
-
vaultData.getSize(),
|
|
710
|
+
if (!AmountMath.isEmpty(plan.phantomDebt)) {
|
|
711
|
+
state.totalDebt = AmountMath.subtract(
|
|
712
|
+
state.totalDebt,
|
|
713
|
+
plan.phantomDebt,
|
|
809
714
|
);
|
|
715
|
+
}
|
|
810
716
|
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
717
|
+
facets.helper.burnToCoverDebt(
|
|
718
|
+
plan.debtToBurn,
|
|
719
|
+
plan.mintedProceeds,
|
|
720
|
+
liqSeat,
|
|
721
|
+
);
|
|
722
|
+
if (!AmountMath.isEmpty(plan.mintedForReserve)) {
|
|
723
|
+
facets.helper.sendToReserve(
|
|
724
|
+
plan.mintedForReserve,
|
|
725
|
+
liqSeat,
|
|
726
|
+
'Minted',
|
|
815
727
|
);
|
|
816
|
-
}
|
|
817
|
-
// Flow #2b: There's unsold collateral; some vaults may be revived.
|
|
818
|
-
|
|
819
|
-
facets.helper.burnToCoverDebt(totalDebt, mintedProceeds, liqSeat);
|
|
820
|
-
facets.helper.sendToReserve(accounting.overage, liqSeat, 'Minted');
|
|
728
|
+
}
|
|
821
729
|
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
730
|
+
// send all that's left in the seat
|
|
731
|
+
const collateralInLiqSeat = liqSeat.getCurrentAllocation().Collateral;
|
|
732
|
+
if (!AmountMath.isEmpty(collateralInLiqSeat)) {
|
|
733
|
+
facets.helper.sendToReserve(collateralInLiqSeat, liqSeat);
|
|
734
|
+
}
|
|
735
|
+
// if it didn't match what was expected, report
|
|
736
|
+
if (!AmountMath.isEqual(collateralInLiqSeat, plan.collatRemaining)) {
|
|
737
|
+
console.error(
|
|
738
|
+
`⚠️ Excess collateral remaining sent to reserve. Expected ${q(
|
|
739
|
+
plan.collatRemaining,
|
|
740
|
+
)}, sent ${q(collateralInLiqSeat)}`,
|
|
826
741
|
);
|
|
742
|
+
}
|
|
827
743
|
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
/** @type {import('@agoric/zoe/src/contractSupport/atomicTransfer.js').TransferPart[]} */
|
|
835
|
-
const transfers = [];
|
|
836
|
-
let liquidated = 0;
|
|
837
|
-
/** @type {Array<Vault>} */
|
|
838
|
-
const vaultsToReinstate = [];
|
|
839
|
-
const reinstateAll = () => {
|
|
840
|
-
const { prioritizedVaults } = collateralEphemera(collateralBrand);
|
|
841
|
-
for (const vault of vaultsToReinstate) {
|
|
842
|
-
const vaultId = vault.abortLiquidation();
|
|
843
|
-
prioritizedVaults.addVault(vaultId, vault);
|
|
844
|
-
liquidatingVaults.delete(vault);
|
|
845
|
-
}
|
|
846
|
-
};
|
|
847
|
-
|
|
848
|
-
let collateralReduction = AmountMath.makeEmpty(collateralBrand);
|
|
849
|
-
let shortfallToReserve = accounting.shortfall;
|
|
850
|
-
const debtPortion = makeRatioFromAmounts(totalPenalty, totalDebt);
|
|
851
|
-
const reduceCollateral = amount =>
|
|
852
|
-
(collateralReduction = AmountMath.add(
|
|
853
|
-
collateralReduction,
|
|
854
|
-
amount,
|
|
855
|
-
));
|
|
856
|
-
|
|
857
|
-
// iterate from best to worst attempting to reconstitute, by
|
|
858
|
-
// returning remaining funds to vaults
|
|
859
|
-
/** @type {Array<[Vault, { collateralAmount: Amount<'nat'>, debtAmount: Amount<'nat'>}]>} */
|
|
860
|
-
for (const [vault, balance] of bestToWorst) {
|
|
861
|
-
const { collateralAmount: vCollat, debtAmount } = balance;
|
|
862
|
-
const vaultPenalty = ceilMultiplyBy(debtAmount, penaltyRate);
|
|
863
|
-
const collatPostPenalty = AmountMath.subtract(
|
|
864
|
-
vCollat,
|
|
865
|
-
ceilMultiplyBy(vaultPenalty, debtPortion),
|
|
866
|
-
);
|
|
867
|
-
const vaultDebt = floorMultiplyBy(debtAmount, debtPortion);
|
|
868
|
-
if (
|
|
869
|
-
reconstituteVaults &&
|
|
870
|
-
!AmountMath.isEmpty(collatPostPenalty) &&
|
|
871
|
-
AmountMath.isGTE(collatRemaining, collatPostPenalty) &&
|
|
872
|
-
AmountMath.isGTE(totalDebt, debtAmount)
|
|
873
|
-
) {
|
|
874
|
-
collatRemaining = AmountMath.subtract(
|
|
875
|
-
collatRemaining,
|
|
876
|
-
collatPostPenalty,
|
|
877
|
-
);
|
|
878
|
-
shortfallToReserve = AmountMath.isGTE(
|
|
879
|
-
shortfallToReserve,
|
|
880
|
-
debtAmount,
|
|
881
|
-
)
|
|
882
|
-
? AmountMath.subtract(shortfallToReserve, debtAmount)
|
|
883
|
-
: AmountMath.makeEmptyFromAmount(shortfallToReserve);
|
|
884
|
-
const seat = vault.getVaultSeat();
|
|
885
|
-
// must reinstate after atomicRearrange(), so we record them.
|
|
886
|
-
vaultsToReinstate.push(vault);
|
|
887
|
-
reduceCollateral(vaultDebt);
|
|
888
|
-
transfers.push([
|
|
889
|
-
liqSeat,
|
|
890
|
-
seat,
|
|
891
|
-
{ Collateral: collatPostPenalty },
|
|
892
|
-
]);
|
|
893
|
-
} else {
|
|
894
|
-
reconstituteVaults = false;
|
|
895
|
-
liquidated += 1;
|
|
896
|
-
facets.helper.subtractPhantomInterest(
|
|
897
|
-
debtAmount,
|
|
898
|
-
vault.getCurrentDebt(),
|
|
899
|
-
);
|
|
900
|
-
|
|
901
|
-
reduceCollateral(vCollat);
|
|
902
|
-
vaultsToLiquidate.push(vault);
|
|
903
|
-
}
|
|
904
|
-
}
|
|
905
|
-
|
|
906
|
-
// Putting all the rearrangements after the loop ensures that errors
|
|
907
|
-
// in the calculations don't result in paying pack some vaults and
|
|
908
|
-
// leaving others hanging.
|
|
909
|
-
if (transfers.length > 0) {
|
|
910
|
-
atomicRearrange(zcf, harden(transfers));
|
|
911
|
-
}
|
|
912
|
-
reinstateAll();
|
|
744
|
+
// 'totalCollateralSold' is only for this liquidation event
|
|
745
|
+
// 'state.totalCollateralSold' represents all active vaults
|
|
746
|
+
const actualCollateralSold = plan.actualCollateralSold;
|
|
747
|
+
state.totalCollateral = AmountMath.isEmpty(actualCollateralSold)
|
|
748
|
+
? AmountMath.subtract(state.totalCollateral, totalCollateral)
|
|
749
|
+
: AmountMath.subtract(state.totalCollateral, actualCollateralSold);
|
|
913
750
|
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
751
|
+
facets.helper.markDoneLiquidating(
|
|
752
|
+
totalDebt,
|
|
753
|
+
totalCollateral,
|
|
754
|
+
plan.overage,
|
|
755
|
+
plan.shortfallToReserve,
|
|
756
|
+
);
|
|
919
757
|
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
...accounting,
|
|
923
|
-
shortfall: shortfallToReserve,
|
|
924
|
-
});
|
|
925
|
-
}
|
|
926
|
-
liquidateAll();
|
|
927
|
-
return facets.helper.writeMetrics();
|
|
758
|
+
// liqSeat should be empty at this point, except that funds are sent
|
|
759
|
+
// asynchronously to the reserve.
|
|
928
760
|
},
|
|
929
761
|
},
|
|
930
762
|
|
|
@@ -1198,7 +1030,7 @@ export const prepareVaultManagerKit = (
|
|
|
1198
1030
|
},
|
|
1199
1031
|
|
|
1200
1032
|
lockOraclePrices() {
|
|
1201
|
-
const { state } = this;
|
|
1033
|
+
const { state, facets } = this;
|
|
1202
1034
|
const { storedCollateralQuote } = collateralEphemera(
|
|
1203
1035
|
state.collateralBrand,
|
|
1204
1036
|
);
|
|
@@ -1211,6 +1043,7 @@ export const prepareVaultManagerKit = (
|
|
|
1211
1043
|
);
|
|
1212
1044
|
|
|
1213
1045
|
state.lockedQuote = storedCollateralQuote;
|
|
1046
|
+
facets.helper.writeMetrics();
|
|
1214
1047
|
return storedCollateralQuote;
|
|
1215
1048
|
},
|
|
1216
1049
|
/**
|
|
@@ -1236,6 +1069,7 @@ export const prepareVaultManagerKit = (
|
|
|
1236
1069
|
|
|
1237
1070
|
const liqMargin = self.getGovernedParams().getLiquidationMargin();
|
|
1238
1071
|
|
|
1072
|
+
// totals *among* vaults being liquidated
|
|
1239
1073
|
const { totalDebt, totalCollateral, vaultData, liqSeat } =
|
|
1240
1074
|
getLiquidatableVaults(
|
|
1241
1075
|
zcf,
|
|
@@ -1288,14 +1122,35 @@ export const prepareVaultManagerKit = (
|
|
|
1288
1122
|
);
|
|
1289
1123
|
|
|
1290
1124
|
trace(`LiqV after long wait`, proceeds);
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1125
|
+
try {
|
|
1126
|
+
const { plan, vaultsInPlan } = helper.planProceedsDistribution(
|
|
1127
|
+
proceeds,
|
|
1128
|
+
totalDebt,
|
|
1129
|
+
storedCollateralQuote,
|
|
1130
|
+
vaultData,
|
|
1131
|
+
totalCollateral,
|
|
1132
|
+
);
|
|
1133
|
+
trace('PLAN', plan);
|
|
1134
|
+
// distributeProceeds may reconstitute vaults, removing them from liquidatingVaults
|
|
1135
|
+
helper.distributeProceeds({
|
|
1136
|
+
liqSeat,
|
|
1137
|
+
plan,
|
|
1138
|
+
totalCollateral,
|
|
1139
|
+
totalDebt,
|
|
1140
|
+
vaultsInPlan,
|
|
1141
|
+
});
|
|
1142
|
+
} catch (err) {
|
|
1143
|
+
console.error('🚨 Error distributing proceeds:', err);
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
// for all non-reconstituted vaults, transition to 'liquidated' state
|
|
1147
|
+
state.numLiquidationsCompleted += liquidatingVaults.getSize();
|
|
1148
|
+
for (const vault of liquidatingVaults.values()) {
|
|
1149
|
+
vault.liquidated();
|
|
1150
|
+
liquidatingVaults.delete(vault);
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
await facets.helper.writeMetrics();
|
|
1299
1154
|
},
|
|
1300
1155
|
},
|
|
1301
1156
|
},
|