@agoric/inter-protocol 0.14.1-dev-effbf7f.0 → 0.14.1-dev-72b0aa9.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-auctioneer-js-meta.json +116 -116
- package/bundles/bundle-auctioneer.js +1 -1
- package/bundles/bundle-econCommitteeCharter-js-meta.json +71 -71
- package/bundles/bundle-feeDistributor-js-meta.json +57 -57
- package/bundles/bundle-fluxAggregatorKit-js-meta.json +111 -111
- package/bundles/bundle-psm-js-meta.json +109 -109
- package/bundles/bundle-psm.js +1 -1
- package/bundles/bundle-reserve-js-meta.json +108 -108
- package/bundles/bundle-scaledPriceAuthority-js-meta.json +78 -78
- package/bundles/bundle-vaultFactory-js-meta.json +136 -132
- package/bundles/bundle-vaultFactory.js +1 -1
- package/package.json +17 -17
- package/src/contractSupport.js +16 -0
- package/src/vaultFactory/Liquidation.md +1 -1
- package/src/vaultFactory/liquidation.js +8 -23
- package/src/vaultFactory/proceeds.js +274 -0
- package/src/vaultFactory/vaultDirector.js +1 -1
- package/src/vaultFactory/vaultFactory.js +3 -3
- package/src/vaultFactory/vaultManager.js +163 -340
|
@@ -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,12 +51,17 @@ 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
66
|
const { details: X, Fail, quote: q } = assert;
|
|
66
67
|
|
|
@@ -154,10 +155,6 @@ const trace = makeTracer('VM');
|
|
|
154
155
|
* }} MutableState
|
|
155
156
|
*/
|
|
156
157
|
|
|
157
|
-
/** @param {Pick<PriceDescription, 'amountIn' | 'amountOut'>} quoteAmount */
|
|
158
|
-
const quoteAsRatio = quoteAmount =>
|
|
159
|
-
makeRatioFromAmounts(quoteAmount.amountIn, quoteAmount.amountOut);
|
|
160
|
-
|
|
161
158
|
/**
|
|
162
159
|
* @type {(brand: Brand) => {
|
|
163
160
|
* prioritizedVaults: ReturnType<typeof makePrioritizedVaults>,
|
|
@@ -496,26 +493,17 @@ export const prepareVaultManagerKit = (
|
|
|
496
493
|
// don't wait for response
|
|
497
494
|
void E.when(invitation, invite => {
|
|
498
495
|
const proposal = { give: { Collateral: penalty } };
|
|
499
|
-
|
|
496
|
+
return offerTo(
|
|
500
497
|
zcf,
|
|
501
498
|
invite,
|
|
502
499
|
{ [seatKeyword]: 'Collateral' },
|
|
503
500
|
proposal,
|
|
504
501
|
seat,
|
|
505
502
|
);
|
|
503
|
+
}).catch(reason => {
|
|
504
|
+
console.error('sendToReserve failed', reason);
|
|
506
505
|
});
|
|
507
506
|
},
|
|
508
|
-
/** @type {(collatSold: Amount<'nat'>, completed: number, aborted?: number) => void} */
|
|
509
|
-
markCollateralLiquidated(collatSold, completed, aborted = 0) {
|
|
510
|
-
const { state } = this;
|
|
511
|
-
|
|
512
|
-
state.totalCollateral = AmountMath.subtract(
|
|
513
|
-
state.totalCollateral,
|
|
514
|
-
collatSold,
|
|
515
|
-
);
|
|
516
|
-
state.numLiquidationsCompleted += completed;
|
|
517
|
-
state.numLiquidationsAborted += aborted;
|
|
518
|
-
},
|
|
519
507
|
markLiquidating(debt, collateral) {
|
|
520
508
|
const { state } = this;
|
|
521
509
|
|
|
@@ -530,9 +518,10 @@ export const prepareVaultManagerKit = (
|
|
|
530
518
|
*
|
|
531
519
|
* @param {Amount<'nat'>} debt
|
|
532
520
|
* @param {Amount<'nat'>} collateral
|
|
533
|
-
* @param {
|
|
521
|
+
* @param {Amount<'nat'>} overage
|
|
522
|
+
* @param {Amount<'nat'>} shortfall
|
|
534
523
|
*/
|
|
535
|
-
markDoneLiquidating(debt, collateral,
|
|
524
|
+
markDoneLiquidating(debt, collateral, overage, shortfall) {
|
|
536
525
|
const { state } = this;
|
|
537
526
|
|
|
538
527
|
// update liquidation state
|
|
@@ -548,7 +537,6 @@ export const prepareVaultManagerKit = (
|
|
|
548
537
|
|
|
549
538
|
// record shortfall and proceeds
|
|
550
539
|
|
|
551
|
-
const { overage, shortfall } = accounting;
|
|
552
540
|
// cumulative values
|
|
553
541
|
state.totalOverageReceived = AmountMath.add(
|
|
554
542
|
state.totalOverageReceived,
|
|
@@ -572,25 +560,6 @@ export const prepareVaultManagerKit = (
|
|
|
572
560
|
console.error('🚨 failed to report liquidation shortfall', err);
|
|
573
561
|
});
|
|
574
562
|
},
|
|
575
|
-
/**
|
|
576
|
-
* If interest was charged between liquidating and liquidated, erase it.
|
|
577
|
-
*
|
|
578
|
-
* @param {Amount<'nat'>} priorDebtAmount
|
|
579
|
-
* @param {Amount<'nat'>} currentDebt
|
|
580
|
-
*/
|
|
581
|
-
subtractPhantomInterest(priorDebtAmount, currentDebt) {
|
|
582
|
-
const { state } = this;
|
|
583
|
-
const difference = AmountMath.subtract(currentDebt, priorDebtAmount);
|
|
584
|
-
|
|
585
|
-
if (!AmountMath.isEmpty(difference)) {
|
|
586
|
-
state.totalDebt = AmountMath.subtract(state.totalDebt, difference);
|
|
587
|
-
}
|
|
588
|
-
},
|
|
589
|
-
markRestoreDebt(debt) {
|
|
590
|
-
const { state } = this;
|
|
591
|
-
|
|
592
|
-
state.totalDebt = AmountMath.add(state.totalDebt, debt);
|
|
593
|
-
},
|
|
594
563
|
writeMetrics() {
|
|
595
564
|
const { state } = this;
|
|
596
565
|
const { collateralBrand, retainedCollateralSeat, metricsTopicKit } =
|
|
@@ -628,16 +597,25 @@ export const prepareVaultManagerKit = (
|
|
|
628
597
|
return E(metricsTopicKit.recorder).write(payload);
|
|
629
598
|
},
|
|
630
599
|
|
|
631
|
-
|
|
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(
|
|
632
612
|
proceeds,
|
|
633
613
|
totalDebt,
|
|
634
614
|
oraclePriceAtStart,
|
|
635
|
-
liqSeat,
|
|
636
615
|
vaultData,
|
|
637
616
|
totalCollateral,
|
|
638
617
|
) {
|
|
639
618
|
const { state, facets } = this;
|
|
640
|
-
const { collateralBrand, debtBrand, liquidatingVaults } = this.state;
|
|
641
619
|
|
|
642
620
|
const { Collateral: collateralProceeds } = proceeds;
|
|
643
621
|
/** @type {Amount<'nat'>} */
|
|
@@ -650,312 +628,135 @@ export const prepareVaultManagerKit = (
|
|
|
650
628
|
collateralSold,
|
|
651
629
|
);
|
|
652
630
|
|
|
653
|
-
const mintedProceeds =
|
|
654
|
-
proceeds.Minted || AmountMath.makeEmpty(debtBrand);
|
|
655
|
-
const accounting = liquidationResults(totalDebt, mintedProceeds);
|
|
656
|
-
|
|
657
|
-
/** @type {Array<Vault>} */
|
|
658
|
-
const vaultsToLiquidate = [];
|
|
659
|
-
const liquidateAll = () => {
|
|
660
|
-
for (const vault of vaultsToLiquidate) {
|
|
661
|
-
vault.liquidated();
|
|
662
|
-
liquidatingVaults.delete(vault);
|
|
663
|
-
}
|
|
664
|
-
};
|
|
665
|
-
|
|
666
631
|
const penaltyRate = facets.self
|
|
667
632
|
.getGovernedParams()
|
|
668
633
|
.getLiquidationPenalty();
|
|
669
|
-
// charged in collateral
|
|
670
|
-
const totalPenalty = ceilMultiplyBy(
|
|
671
|
-
totalDebt,
|
|
672
|
-
multiplyRatios(
|
|
673
|
-
penaltyRate,
|
|
674
|
-
quoteAsRatio(oraclePriceAtStart.quoteAmount.value[0]),
|
|
675
|
-
),
|
|
676
|
-
);
|
|
677
|
-
const debtPortion = makeRatioFromAmounts(totalPenalty, totalDebt);
|
|
678
|
-
|
|
679
|
-
// Liquidation.md describes how to process liquidation proceeds
|
|
680
634
|
const bestToWorst = [...vaultData.entries()].reverse();
|
|
681
|
-
if (AmountMath.isEmpty(accounting.shortfall)) {
|
|
682
|
-
// Flow #1: no shortfall
|
|
683
|
-
|
|
684
|
-
const collateralToDistribute = AmountMath.isGTE(
|
|
685
|
-
collateralProceeds,
|
|
686
|
-
totalPenalty,
|
|
687
|
-
);
|
|
688
|
-
|
|
689
|
-
const distributableCollateral = collateralToDistribute
|
|
690
|
-
? AmountMath.subtract(collateralProceeds, totalPenalty)
|
|
691
|
-
: AmountMath.makeEmptyFromAmount(collateralProceeds);
|
|
692
|
-
|
|
693
|
-
facets.helper.burnToCoverDebt(totalDebt, mintedProceeds, liqSeat);
|
|
694
|
-
facets.helper.sendToReserve(accounting.overage, liqSeat, 'Minted');
|
|
695
635
|
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
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);
|
|
710
651
|
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
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
|
+
},
|
|
715
662
|
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
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} */ ([
|
|
728
691
|
liqSeat,
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
])
|
|
732
|
-
}
|
|
733
|
-
vaultsToLiquidate.push(vault);
|
|
734
|
-
}
|
|
735
|
-
if (transfers.length > 0) {
|
|
736
|
-
atomicRearrange(zcf, harden(transfers));
|
|
737
|
-
}
|
|
738
|
-
|
|
739
|
-
const forReserve = collateralToDistribute
|
|
740
|
-
? AmountMath.add(leftToStage, totalPenalty)
|
|
741
|
-
: collateralProceeds;
|
|
742
|
-
facets.helper.sendToReserve(forReserve, liqSeat);
|
|
743
|
-
|
|
744
|
-
facets.helper.markCollateralLiquidated(
|
|
745
|
-
totalCollateral,
|
|
746
|
-
vaultData.getSize(),
|
|
747
|
-
);
|
|
748
|
-
|
|
749
|
-
facets.helper.markDoneLiquidating(
|
|
750
|
-
totalDebt,
|
|
751
|
-
totalCollateral,
|
|
752
|
-
accounting,
|
|
753
|
-
);
|
|
754
|
-
} else if (AmountMath.isEmpty(collateralProceeds)) {
|
|
755
|
-
// Flow #2a
|
|
756
|
-
|
|
757
|
-
// charge penalty if proceeds are sufficient
|
|
758
|
-
const penaltyInMinted = ceilMultiplyBy(totalDebt, penaltyRate);
|
|
759
|
-
const recoveredDebt = AmountMath.min(
|
|
760
|
-
AmountMath.add(totalDebt, penaltyInMinted),
|
|
761
|
-
mintedProceeds,
|
|
762
|
-
);
|
|
763
|
-
|
|
764
|
-
facets.helper.burnToCoverDebt(
|
|
765
|
-
recoveredDebt,
|
|
766
|
-
mintedProceeds,
|
|
767
|
-
liqSeat,
|
|
692
|
+
vaultsInPlan[vaultIndex].getVaultSeat(),
|
|
693
|
+
amounts,
|
|
694
|
+
]),
|
|
768
695
|
);
|
|
696
|
+
atomicRearrange(zcf, harden(transfers));
|
|
697
|
+
}
|
|
769
698
|
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
const
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
/** @type {import('@agoric/zoe/src/contractSupport/atomicTransfer.js').TransferPart[]} */
|
|
781
|
-
const transfers = [];
|
|
782
|
-
|
|
783
|
-
// iterate from best to worst returning remaining funds to vaults
|
|
784
|
-
/** @type {Array<[Vault, { collateralAmount: Amount<'nat'>, debtAmount: Amount<'nat'>}]>} */
|
|
785
|
-
for (const [vault, balance] of bestToWorst) {
|
|
786
|
-
// from best to worst, return minted above penalty if any remains
|
|
787
|
-
const { collateralAmount: vCollat, debtAmount } = balance;
|
|
788
|
-
const vaultShare = floorMultiplyBy(vCollat, vaultPortion);
|
|
789
|
-
facets.helper.subtractPhantomInterest(
|
|
790
|
-
debtAmount,
|
|
791
|
-
vault.getCurrentDebt(),
|
|
792
|
-
);
|
|
793
|
-
|
|
794
|
-
if (!AmountMath.isEmpty(mintedRemaining)) {
|
|
795
|
-
const mintedToReturn = AmountMath.isGTE(
|
|
796
|
-
mintedRemaining,
|
|
797
|
-
vaultShare,
|
|
798
|
-
)
|
|
799
|
-
? vaultShare
|
|
800
|
-
: mintedRemaining;
|
|
801
|
-
mintedRemaining = AmountMath.subtract(
|
|
802
|
-
mintedRemaining,
|
|
803
|
-
mintedToReturn,
|
|
804
|
-
);
|
|
805
|
-
const seat = vault.getVaultSeat();
|
|
806
|
-
transfers.push([liqSeat, seat, { Minted: mintedToReturn }]);
|
|
807
|
-
}
|
|
808
|
-
vaultsToLiquidate.push(vault);
|
|
809
|
-
}
|
|
810
|
-
|
|
811
|
-
if (transfers.length > 0) {
|
|
812
|
-
atomicRearrange(zcf, harden(transfers));
|
|
813
|
-
}
|
|
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
|
+
}
|
|
814
709
|
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
710
|
+
if (!AmountMath.isEmpty(plan.phantomDebt)) {
|
|
711
|
+
state.totalDebt = AmountMath.subtract(
|
|
712
|
+
state.totalDebt,
|
|
713
|
+
plan.phantomDebt,
|
|
818
714
|
);
|
|
715
|
+
}
|
|
819
716
|
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
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',
|
|
824
727
|
);
|
|
825
|
-
}
|
|
826
|
-
// Flow #2b: There's unsold collateral; some vaults may be revived.
|
|
827
|
-
|
|
828
|
-
facets.helper.burnToCoverDebt(totalDebt, mintedProceeds, liqSeat);
|
|
829
|
-
facets.helper.sendToReserve(accounting.overage, liqSeat, 'Minted');
|
|
728
|
+
}
|
|
830
729
|
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
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)}`,
|
|
835
741
|
);
|
|
742
|
+
}
|
|
836
743
|
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
/** @type {import('@agoric/zoe/src/contractSupport/atomicTransfer.js').TransferPart[]} */
|
|
844
|
-
const transfers = [];
|
|
845
|
-
let liquidated = 0;
|
|
846
|
-
/** @type {Array<Vault>} */
|
|
847
|
-
const vaultsToReinstate = [];
|
|
848
|
-
const reinstateAll = () => {
|
|
849
|
-
const { prioritizedVaults } = collateralEphemera(collateralBrand);
|
|
850
|
-
for (const vault of vaultsToReinstate) {
|
|
851
|
-
const vaultId = vault.abortLiquidation();
|
|
852
|
-
prioritizedVaults.addVault(vaultId, vault);
|
|
853
|
-
liquidatingVaults.delete(vault);
|
|
854
|
-
}
|
|
855
|
-
};
|
|
856
|
-
|
|
857
|
-
let collateralReduction = AmountMath.makeEmpty(collateralBrand);
|
|
858
|
-
let shortfallToReserve = accounting.shortfall;
|
|
859
|
-
const reduceCollateral = amount =>
|
|
860
|
-
(collateralReduction = AmountMath.add(
|
|
861
|
-
collateralReduction,
|
|
862
|
-
amount,
|
|
863
|
-
));
|
|
864
|
-
|
|
865
|
-
// iterate from best to worst attempting to reconstitute, by
|
|
866
|
-
// returning remaining funds to vaults
|
|
867
|
-
/** @type {Array<[Vault, { collateralAmount: Amount<'nat'>, debtAmount: Amount<'nat'>}]>} */
|
|
868
|
-
for (const [vault, balance] of bestToWorst) {
|
|
869
|
-
const { collateralAmount: vCollat, debtAmount } = balance;
|
|
870
|
-
|
|
871
|
-
// according to #7123, Collateral for penalty =
|
|
872
|
-
// vault debt / total debt * total liquidation penalty
|
|
873
|
-
const vaultPenalty = ceilMultiplyBy(
|
|
874
|
-
debtAmount,
|
|
875
|
-
makeRatioFromAmounts(totalPenalty, totalDebt),
|
|
876
|
-
);
|
|
877
|
-
const collatPostPenalty = AmountMath.subtract(
|
|
878
|
-
vCollat,
|
|
879
|
-
vaultPenalty,
|
|
880
|
-
);
|
|
881
|
-
const vaultDebt = floorMultiplyBy(debtAmount, debtPortion);
|
|
882
|
-
if (
|
|
883
|
-
reconstituteVaults &&
|
|
884
|
-
!AmountMath.isEmpty(collatPostPenalty) &&
|
|
885
|
-
AmountMath.isGTE(collatRemaining, collatPostPenalty) &&
|
|
886
|
-
AmountMath.isGTE(totalDebt, debtAmount)
|
|
887
|
-
) {
|
|
888
|
-
collatRemaining = AmountMath.subtract(
|
|
889
|
-
collatRemaining,
|
|
890
|
-
collatPostPenalty,
|
|
891
|
-
);
|
|
892
|
-
shortfallToReserve = AmountMath.isGTE(
|
|
893
|
-
shortfallToReserve,
|
|
894
|
-
debtAmount,
|
|
895
|
-
)
|
|
896
|
-
? AmountMath.subtract(shortfallToReserve, debtAmount)
|
|
897
|
-
: AmountMath.makeEmptyFromAmount(shortfallToReserve);
|
|
898
|
-
const seat = vault.getVaultSeat();
|
|
899
|
-
// must reinstate after atomicRearrange(), so we record them.
|
|
900
|
-
vaultsToReinstate.push(vault);
|
|
901
|
-
reduceCollateral(vaultDebt);
|
|
902
|
-
transfers.push([
|
|
903
|
-
liqSeat,
|
|
904
|
-
seat,
|
|
905
|
-
{ Collateral: collatPostPenalty },
|
|
906
|
-
]);
|
|
907
|
-
} else {
|
|
908
|
-
reconstituteVaults = false;
|
|
909
|
-
liquidated += 1;
|
|
910
|
-
facets.helper.subtractPhantomInterest(
|
|
911
|
-
debtAmount,
|
|
912
|
-
vault.getCurrentDebt(),
|
|
913
|
-
);
|
|
914
|
-
|
|
915
|
-
reduceCollateral(vCollat);
|
|
916
|
-
vaultsToLiquidate.push(vault);
|
|
917
|
-
}
|
|
918
|
-
}
|
|
919
|
-
|
|
920
|
-
// Putting all the rearrangements after the loop ensures that errors
|
|
921
|
-
// in the calculations don't result in paying back some vaults and
|
|
922
|
-
// leaving others hanging.
|
|
923
|
-
if (transfers.length > 0) {
|
|
924
|
-
atomicRearrange(zcf, harden(transfers));
|
|
925
|
-
}
|
|
926
|
-
reinstateAll();
|
|
927
|
-
|
|
928
|
-
facets.helper.markCollateralLiquidated(
|
|
929
|
-
collateralReduction,
|
|
930
|
-
liquidated,
|
|
931
|
-
transfers.length,
|
|
932
|
-
);
|
|
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);
|
|
933
750
|
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
751
|
+
facets.helper.markDoneLiquidating(
|
|
752
|
+
totalDebt,
|
|
753
|
+
totalCollateral,
|
|
754
|
+
plan.overage,
|
|
755
|
+
plan.shortfallToReserve,
|
|
756
|
+
);
|
|
937
757
|
|
|
938
|
-
if (
|
|
939
|
-
!AmountMath.isEqual(
|
|
940
|
-
collateralInLiqSeat,
|
|
941
|
-
AmountMath.add(collatRemaining, totalPenalty),
|
|
942
|
-
)
|
|
943
|
-
) {
|
|
944
|
-
console.error(
|
|
945
|
-
`🚨 Excess collateral remaining sent to reserve. Expected ${q(
|
|
946
|
-
collatRemaining,
|
|
947
|
-
)}, sent ${q(collateralInLiqSeat)}`,
|
|
948
|
-
);
|
|
949
|
-
}
|
|
950
|
-
facets.helper.markDoneLiquidating(totalDebt, totalCollateral, {
|
|
951
|
-
...accounting,
|
|
952
|
-
shortfall: shortfallToReserve,
|
|
953
|
-
});
|
|
954
|
-
}
|
|
955
758
|
// liqSeat should be empty at this point, except that funds are sent
|
|
956
759
|
// asynchronously to the reserve.
|
|
957
|
-
liquidateAll();
|
|
958
|
-
return facets.helper.writeMetrics();
|
|
959
760
|
},
|
|
960
761
|
},
|
|
961
762
|
|
|
@@ -1268,6 +1069,7 @@ export const prepareVaultManagerKit = (
|
|
|
1268
1069
|
|
|
1269
1070
|
const liqMargin = self.getGovernedParams().getLiquidationMargin();
|
|
1270
1071
|
|
|
1072
|
+
// totals *among* vaults being liquidated
|
|
1271
1073
|
const { totalDebt, totalCollateral, vaultData, liqSeat } =
|
|
1272
1074
|
getLiquidatableVaults(
|
|
1273
1075
|
zcf,
|
|
@@ -1320,14 +1122,35 @@ export const prepareVaultManagerKit = (
|
|
|
1320
1122
|
);
|
|
1321
1123
|
|
|
1322
1124
|
trace(`LiqV after long wait`, proceeds);
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
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();
|
|
1331
1154
|
},
|
|
1332
1155
|
},
|
|
1333
1156
|
},
|