@agoric/inter-protocol 0.14.1-dev-13c169c.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agoric/inter-protocol",
3
- "version": "0.14.1-dev-13c169c.0+13c169c",
3
+ "version": "0.14.1-dev-72b0aa9.0+72b0aa9",
4
4
  "description": "Core cryptoeconomy contracts",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -29,29 +29,29 @@
29
29
  },
30
30
  "homepage": "https://github.com/Agoric/agoric-sdk#readme",
31
31
  "dependencies": {
32
- "@agoric/assert": "0.6.1-dev-13c169c.0+13c169c",
33
- "@agoric/ertp": "0.16.1-dev-13c169c.0+13c169c",
34
- "@agoric/governance": "0.10.1-dev-13c169c.0+13c169c",
35
- "@agoric/internal": "0.3.1-dev-13c169c.0+13c169c",
36
- "@agoric/notifier": "0.6.1-dev-13c169c.0+13c169c",
37
- "@agoric/store": "0.9.1-dev-13c169c.0+13c169c",
38
- "@agoric/time": "0.3.1-dev-13c169c.0+13c169c",
39
- "@agoric/vat-data": "0.5.1-dev-13c169c.0+13c169c",
40
- "@agoric/vats": "0.14.1-dev-13c169c.0+13c169c",
41
- "@agoric/zoe": "0.26.1-dev-13c169c.0+13c169c",
32
+ "@agoric/assert": "0.6.1-dev-72b0aa9.0+72b0aa9",
33
+ "@agoric/ertp": "0.16.1-dev-72b0aa9.0+72b0aa9",
34
+ "@agoric/governance": "0.10.1-dev-72b0aa9.0+72b0aa9",
35
+ "@agoric/internal": "0.3.1-dev-72b0aa9.0+72b0aa9",
36
+ "@agoric/notifier": "0.6.1-dev-72b0aa9.0+72b0aa9",
37
+ "@agoric/store": "0.9.1-dev-72b0aa9.0+72b0aa9",
38
+ "@agoric/time": "0.3.1-dev-72b0aa9.0+72b0aa9",
39
+ "@agoric/vat-data": "0.5.1-dev-72b0aa9.0+72b0aa9",
40
+ "@agoric/vats": "0.14.1-dev-72b0aa9.0+72b0aa9",
41
+ "@agoric/zoe": "0.26.1-dev-72b0aa9.0+72b0aa9",
42
42
  "@endo/captp": "^3.1.1",
43
43
  "@endo/eventual-send": "^0.17.2",
44
44
  "@endo/far": "^0.2.18",
45
45
  "@endo/marshal": "^0.8.5",
46
46
  "@endo/nat": "^4.1.27",
47
- "agoric": "0.19.1-dev-13c169c.0+13c169c",
47
+ "agoric": "0.19.1-dev-72b0aa9.0+72b0aa9",
48
48
  "jessie.js": "^0.3.2"
49
49
  },
50
50
  "devDependencies": {
51
- "@agoric/deploy-script-support": "0.10.1-dev-13c169c.0+13c169c",
52
- "@agoric/smart-wallet": "0.5.1-dev-13c169c.0+13c169c",
53
- "@agoric/swingset-liveslots": "0.10.1-dev-13c169c.0+13c169c",
54
- "@agoric/swingset-vat": "0.32.1-dev-13c169c.0+13c169c",
51
+ "@agoric/deploy-script-support": "0.10.1-dev-72b0aa9.0+72b0aa9",
52
+ "@agoric/smart-wallet": "0.5.1-dev-72b0aa9.0+72b0aa9",
53
+ "@agoric/swingset-liveslots": "0.10.1-dev-72b0aa9.0+72b0aa9",
54
+ "@agoric/swingset-vat": "0.32.1-dev-72b0aa9.0+72b0aa9",
55
55
  "@endo/bundle-source": "^2.5.1",
56
56
  "@endo/init": "^0.5.56",
57
57
  "@endo/promise-kit": "^0.2.56",
@@ -79,5 +79,5 @@
79
79
  "publishConfig": {
80
80
  "access": "public"
81
81
  },
82
- "gitHead": "13c169c76c103c45fb952d4e3362a40a8b56a0d2"
82
+ "gitHead": "72b0aa9ce03797b76d8d671b010d1a1615b5ae45"
83
83
  }
@@ -1,9 +1,8 @@
1
1
  // @jessie-check
2
2
 
3
3
  import { AmountMath } from '@agoric/ertp';
4
- import { makeStoredPublisherKit, makeStoredPublishKit } from '@agoric/notifier';
5
4
  import { M } from '@agoric/store';
6
- import { E } from '@endo/eventual-send';
5
+ import { makeRatioFromAmounts } from '@agoric/zoe/src/contractSupport/index.js';
7
6
 
8
7
  const { Fail, quote: q } = assert;
9
8
 
@@ -29,6 +28,17 @@ export const ratioPattern = harden({
29
28
  export const addSubtract = (base, gain, loss) =>
30
29
  AmountMath.subtract(AmountMath.add(base, gain), loss);
31
30
 
31
+ /**
32
+ * @template {Amount} T
33
+ * @param {T} left
34
+ * @param {T} right
35
+ * @returns {T}
36
+ */
37
+ export const subtractToEmpty = (left, right) =>
38
+ AmountMath.isGTE(right, left)
39
+ ? /** @type {T} */ (AmountMath.makeEmptyFromAmount(left))
40
+ : /** @type {T} */ (AmountMath.subtract(left, right));
41
+
32
42
  /**
33
43
  * Verifies that every key in the proposal is in the provided list
34
44
  *
@@ -65,27 +75,6 @@ export const checkDebtLimit = (debtLimit, totalDebt, toMint) => {
65
75
  )} would hit total debt limit ${q(debtLimit)}`;
66
76
  };
67
77
 
68
- /**
69
- * @deprecated incompatible with durability; instead handle vstorage ephemerally on a durable PublishKit
70
- * @template T
71
- * @param {ERef<StorageNode>} storageNode
72
- * @param {ERef<Marshaller>} marshaller
73
- * @returns {MetricsPublisherKit<T>}
74
- */
75
- export const makeMetricsPublisherKit = (storageNode, marshaller) => {
76
- assert(
77
- storageNode && marshaller,
78
- 'makeMetricsPublisherKit missing storageNode or marshaller',
79
- );
80
- /** @type {import('@agoric/notifier').StoredPublisherKit<T>} */
81
- const kit = makeStoredPublisherKit(storageNode, marshaller, 'metrics');
82
- return {
83
- metricsPublication: kit.publisher,
84
- metricsSubscription: kit.subscriber,
85
- };
86
- };
87
- harden(makeMetricsPublisherKit);
88
-
89
78
  /**
90
79
  * @template T
91
80
  * @typedef {object} MetricsPublisherKit<T>
@@ -100,31 +89,13 @@ harden(makeMetricsPublisherKit);
100
89
  * @property {StoredSubscriber<T>} metricsSubscriber
101
90
  */
102
91
 
103
- /**
104
- * @deprecated incompatible with durability; instead handle vstorage ephemerally on a durable PublishKit
105
- * @template T
106
- * @param {ERef<StorageNode>} storageNode
107
- * @param {ERef<Marshaller>} marshaller
108
- * @returns {MetricsPublishKit<T>}
109
- */
110
- export const makeMetricsPublishKit = (storageNode, marshaller) => {
111
- assert(
112
- storageNode && marshaller,
113
- 'makeMetricsPublisherKit missing storageNode or marshaller',
114
- );
115
- const metricsNode = E(storageNode).makeChildNode('metrics');
116
- /** @type {StoredPublishKit<T>} */
117
- const kit = makeStoredPublishKit(metricsNode, marshaller);
118
- return {
119
- metricsPublisher: kit.publisher,
120
- metricsSubscriber: kit.subscriber,
121
- };
122
- };
123
- harden(makeMetricsPublishKit);
124
-
125
92
  /**
126
93
  * @param {Brand} brand must be a 'nat' brand, not checked
127
94
  * @param {NatValue} [min]
128
95
  */
129
96
  export const makeNatAmountShape = (brand, min) =>
130
97
  harden({ brand, value: min ? M.gte(min) : M.nat() });
98
+
99
+ /** @param {Pick<PriceDescription, 'amountIn' | 'amountOut'>} quoteAmount */
100
+ export const quoteAsRatio = quoteAmount =>
101
+ makeRatioFromAmounts(quoteAmount.amountIn, quoteAmount.amountOut);
@@ -5,11 +5,7 @@
5
5
  */
6
6
  import { AmountMath } from '@agoric/ertp';
7
7
  import { assertAllDefined, makeTracer } from '@agoric/internal';
8
- import {
9
- makeNotifierFromSubscriber,
10
- observeNotifier,
11
- SubscriberShape,
12
- } from '@agoric/notifier';
8
+ import { makeNotifierFromSubscriber, observeNotifier } from '@agoric/notifier';
13
9
  import { M, makeScalarBigMapStore, prepareExoClassKit } from '@agoric/vat-data';
14
10
  import {
15
11
  defineRecorderKit,
@@ -208,8 +204,6 @@ export const prepareFluxAggregatorKit = async (
208
204
  creator: M.interface('fluxAggregator creatorFacet', {}, { sloppy: true }),
209
205
  public: M.interface('fluxAggregator publicFacet', {
210
206
  getPriceAuthority: M.call().returns(M.any()),
211
- getSubscriber: M.call().returns(SubscriberShape),
212
- getRoundStartNotifier: M.call().returns(SubscriberShape),
213
207
  getPublicTopics: M.call().returns({
214
208
  quotes: M.any(),
215
209
  latestRound: M.any(),
@@ -346,14 +340,6 @@ export const prepareFluxAggregatorKit = async (
346
340
  getPriceAuthority() {
347
341
  return priceAuthority;
348
342
  },
349
- /** @deprecated use getPublicTopics */
350
- getSubscriber: () => {
351
- return priceKit.subscriber;
352
- },
353
- /** @deprecated use getPublicTopics */
354
- getRoundStartNotifier() {
355
- return latestRoundKit.subscriber;
356
- },
357
343
  getPublicTopics() {
358
344
  return {
359
345
  quotes: makeRecorderTopic(
@@ -519,7 +519,7 @@ export const startAuctioneer = async (
519
519
  },
520
520
  },
521
521
  issuer: {
522
- consume: { [Stable.symbol]: runIssuerP },
522
+ consume: { [Stable.symbol]: stableIssuerP },
523
523
  },
524
524
  },
525
525
  {
@@ -539,11 +539,11 @@ export const startAuctioneer = async (
539
539
 
540
540
  const poserInvitationP = E(electorateCreatorFacet).getPoserInvitation();
541
541
 
542
- const [initialPoserInvitation, electorateInvitationAmount, runIssuer] =
542
+ const [initialPoserInvitation, electorateInvitationAmount, stableIssuer] =
543
543
  await Promise.all([
544
544
  poserInvitationP,
545
545
  E(E(zoe).getInvitationIssuer()).getAmountOf(poserInvitationP),
546
- runIssuerP,
546
+ stableIssuerP,
547
547
  ]);
548
548
 
549
549
  const timerBrand = await E(chainTimerService).getTimerBrand();
@@ -571,7 +571,7 @@ export const startAuctioneer = async (
571
571
  governedContractInstallation: auctionInstallation,
572
572
  governed: {
573
573
  terms: auctionTerms,
574
- issuerKeywordRecord: { Bid: runIssuer },
574
+ issuerKeywordRecord: { Bid: stableIssuer },
575
575
  storageNode,
576
576
  marshaller,
577
577
  label: 'auctioneer',
@@ -5,7 +5,6 @@ import { M, makeScalarBigMapStore, prepareExoClassKit } from '@agoric/vat-data';
5
5
  import { atomicTransfer } from '@agoric/zoe/src/contractSupport/atomicTransfer.js';
6
6
  import {
7
7
  makeRecorderTopic,
8
- SubscriberShape,
9
8
  TopicsRecordShape,
10
9
  } from '@agoric/zoe/src/contractSupport/topics.js';
11
10
  import { AmountKeywordRecordShape } from '@agoric/zoe/src/typeGuards.js';
@@ -57,7 +56,6 @@ export const prepareAssetReserveKit = async (
57
56
  }),
58
57
  public: M.interface('AssetReserve public', {
59
58
  makeAddCollateralInvitation: M.call().returns(M.promise()),
60
- getMetrics: M.call().returns(SubscriberShape),
61
59
  getPublicTopics: M.call().returns(TopicsRecordShape),
62
60
  }),
63
61
  shortfallReportingFacet: M.interface('AssetReserve shortfall reporter', {
@@ -189,7 +187,7 @@ export const prepareAssetReserveKit = async (
189
187
  await zcf.saveIssuer(issuer, keyword);
190
188
  },
191
189
 
192
- /** @deprecated use getPublicTopics metrics allocation */
190
+ /** XXX redundant with getPublicTopics metrics `allocation` */
193
191
  getAllocations() {
194
192
  return this.state.collateralSeat.getCurrentAllocation();
195
193
  },
@@ -239,10 +237,6 @@ export const prepareAssetReserveKit = async (
239
237
  };
240
238
  return zcf.makeInvitation(handler, 'Add Collateral');
241
239
  },
242
- /** @deprecated */
243
- getMetrics() {
244
- return this.state.metricsKit.subscriber;
245
- },
246
240
  getPublicTopics() {
247
241
  return {
248
242
  metrics: makeRecorderTopic(
@@ -90,20 +90,20 @@ sequenceDiagram
90
90
  participant caller
91
91
  participant reserveContract
92
92
  participant collateralSeat
93
- participant runMint
93
+ participant stableMint
94
94
  participant runSeat
95
95
  participant zcf
96
96
  participant ammPublicFacet
97
97
  participant liquidityInvite
98
98
 
99
- caller ->> reserveContract: addLiquidityToAmmPool(collateralAmount, runAmount)
99
+ caller ->> reserveContract: addLiquidityToAmmPool(collateralAmount, stableAmount)
100
100
  reserveContract ->> reserveContract: get collateralKeyword from collateralAmount.brand
101
101
  reserveContract ->> collateralSeat: get current allocation for collateralKeyword
102
102
  reserveContract ->> reserveContract: ensure current allocation for collateralKeyword is >= collateralAmount
103
103
 
104
104
 
105
105
  note right of reserveContract: create the IST
106
- reserveContract ->> runMint: mintGains of runAmount, and get runSeat
106
+ reserveContract ->> stableMint: mintGains of stableAmount, and get runSeat
107
107
  reserveContract ->> runSeat: increment collateralKeyword: collateralAmount
108
108
  reserveContract ->> collateralSeat: decrement collateralKeyword: collateralAmount
109
109
  reserveContract ->> zcf: reallocate(runSeat, collateralSeat)
@@ -112,7 +112,7 @@ note right of reserveContract: Add IST and collateral to the AMM
112
112
  reserveContract ->> ammPublicFacet: get invite for makeAddLiquidityAtRateInvitation
113
113
  reserveContract ->> reserveContract: create mapping, IST = central, collateralKeyword: secondary
114
114
  reserveContract ->> reserveContract: get liquidity brand for collateralKeyword
115
- reserveContract ->> reserveContract: terms mapping, give Central: runAmount, Secondary collateralAmount, want liquidity
115
+ reserveContract ->> reserveContract: terms mapping, give Central: stableAmount, Secondary collateralAmount, want liquidity
116
116
  reserveContract ->> zcf: offer
117
117
 
118
118
 
@@ -13,7 +13,7 @@ There are two interesting exception cases. All the collateral might be sold
13
13
  without covering the debt (flow 2a), or there might not be enough bids to
14
14
  purchase all the collateral (flow 2b). In the first case, vault-holders don't
15
15
  get anything back, we burn the currency we received to cover a portion of the
16
- debt, any excess collateral is sent to the reserve, and we report a shortfall.
16
+ debt, and we report a shortfall.
17
17
 
18
18
  In the second case, we can't tell whether it's due to a downturn in the
19
19
  market or a DoS attack on the exchange. In hopes that it's not the latter, we
@@ -144,7 +144,7 @@ const setWakeups = ({
144
144
  * @param {TimerWaker} reschedulerWaker
145
145
  * @returns {Promise<void>}
146
146
  */
147
- const setWakeupsForNextAuction = async (
147
+ export const setWakeupsForNextAuction = async (
148
148
  auctioneerPublicFacet,
149
149
  timer,
150
150
  priceLockWaker,
@@ -180,35 +180,25 @@ const setWakeupsForNextAuction = async (
180
180
  params,
181
181
  });
182
182
  };
183
+ harden(setWakeupsForNextAuction);
183
184
 
184
185
  /**
185
186
  * @param {Amount<'nat'>} debt
186
187
  * @param {Amount<'nat'>} minted
187
- * @returns {{ overage: Amount<'nat'>, shortfall: Amount<'nat'>, toBurn: Amount<'nat'>}}
188
+ * @returns {{ overage: Amount<'nat'>, shortfall: Amount<'nat'>}}
188
189
  */
189
- const liquidationResults = (debt, minted) => {
190
+ export const liquidationResults = (debt, minted) => {
190
191
  if (AmountMath.isEmpty(minted)) {
191
- return {
192
- overage: minted,
193
- toBurn: minted,
194
- shortfall: debt,
195
- };
192
+ return { overage: minted, shortfall: debt };
196
193
  }
197
194
 
198
195
  const [overage, shortfall] = AmountMath.isGTE(debt, minted)
199
196
  ? [AmountMath.makeEmptyFromAmount(debt), AmountMath.subtract(debt, minted)]
200
197
  : [AmountMath.subtract(minted, debt), AmountMath.makeEmptyFromAmount(debt)];
201
198
 
202
- const toBurn = AmountMath.min(minted, debt);
203
- // debt is fully accounted for, with toBurn and shortfall
204
- assert(AmountMath.isEqual(debt, AmountMath.add(toBurn, shortfall)));
205
-
206
- return {
207
- overage,
208
- toBurn,
209
- shortfall,
210
- };
199
+ return { overage, shortfall };
211
200
  };
201
+ harden(liquidationResults);
212
202
 
213
203
  /**
214
204
  * Watch governed params for change
@@ -260,7 +250,7 @@ export const watchForGovernanceChange = (
260
250
  * totalCollateral: Amount<'nat'>,
261
251
  * liqSeat: ZCFSeat}}
262
252
  */
263
- const getLiquidatableVaults = (
253
+ export const getLiquidatableVaults = (
264
254
  zcf,
265
255
  collateralizationDetails,
266
256
  prioritizedVaults,
@@ -303,9 +293,4 @@ const getLiquidatableVaults = (
303
293
 
304
294
  return { vaultData, totalDebt, totalCollateral, liqSeat };
305
295
  };
306
-
307
- harden(setWakeupsForNextAuction);
308
- harden(liquidationResults);
309
296
  harden(getLiquidatableVaults);
310
-
311
- export { setWakeupsForNextAuction, liquidationResults, getLiquidatableVaults };
@@ -0,0 +1,274 @@
1
+ import { AmountMath } from '@agoric/ertp';
2
+ import {
3
+ ceilDivideBy,
4
+ ceilMultiplyBy,
5
+ floorMultiplyBy,
6
+ makeRatioFromAmounts,
7
+ multiplyRatios,
8
+ } from '@agoric/zoe/src/contractSupport/index.js';
9
+ import { quoteAsRatio, subtractToEmpty } from '../contractSupport.js';
10
+ import { liquidationResults } from './liquidation.js';
11
+
12
+ /**
13
+ * @typedef {{
14
+ * overage: Amount<'nat'>,
15
+ * shortfallToReserve: Amount<'nat'>,
16
+ * collateralForReserve: Amount<'nat'>,
17
+ * actualCollateralSold: Amount<'nat'>,
18
+ * collateralSold: Amount<'nat'>,
19
+ * collatRemaining: Amount<'nat'>,
20
+ * debtToBurn: Amount<'nat'>,
21
+ * mintedForReserve: Amount<'nat'>,
22
+ * mintedProceeds: Amount<'nat'>,
23
+ * phantomDebt: Amount<'nat'>,
24
+ * totalPenalty: Amount<'nat'>,
25
+ * transfersToVault: Array<[number, AmountKeywordRecord]>,
26
+ * vaultsToReinstate: Array<number>
27
+ * }} DistributionPlan
28
+ *
29
+ * The plan to execute for distributing proceeds of a liquidation.
30
+ *
31
+ * Vaults are referenced by index in the list sent to the calculator.
32
+ */
33
+
34
+ /** @typedef {{ collateral: Amount<'nat'>, presaleDebt: Amount<'nat'>, currentDebt: Amount<'nat'> }} VaultBalances */
35
+
36
+ /**
37
+ * Liquidation.md describes how to process liquidation proceeds.
38
+ *
39
+ * This function is complex and may fail. To defend against this possibility, it
40
+ * starts with a base plan and updates it. The updating is the complex portion
41
+ * and is wrapped in a try/catch. If at any point the plan revising fails, the
42
+ * plan is returned as is.
43
+ *
44
+ * @param {object} inputs
45
+ * @param {AmountKeywordRecord} inputs.proceeds
46
+ * @param {Amount<'nat'>} inputs.totalDebt
47
+ * @param {Amount<'nat'>} inputs.totalCollateral
48
+ * @param {PriceDescription} inputs.oraclePriceAtStart
49
+ * @param {Array<VaultBalances>} inputs.vaultsBalances ordered best to worst collateralized
50
+ * @param {Ratio} inputs.penaltyRate
51
+ * @returns {DistributionPlan}
52
+ */
53
+ export const calculateDistributionPlan = ({
54
+ proceeds,
55
+ totalDebt,
56
+ totalCollateral,
57
+ oraclePriceAtStart,
58
+ vaultsBalances,
59
+ penaltyRate,
60
+ }) => {
61
+ const emptyCollateral = AmountMath.makeEmptyFromAmount(totalCollateral);
62
+ const emptyMinted = AmountMath.makeEmptyFromAmount(totalDebt);
63
+
64
+ const { Collateral: collateralProceeds } = proceeds;
65
+ /** @type {Amount<'nat'>} */
66
+ const collateralSold = AmountMath.subtract(
67
+ totalCollateral,
68
+ collateralProceeds,
69
+ );
70
+
71
+ const mintedProceeds = proceeds.Minted || emptyMinted;
72
+ const accounting = liquidationResults(totalDebt, mintedProceeds);
73
+
74
+ // charged in collateral
75
+ const totalPenalty = ceilMultiplyBy(
76
+ totalDebt,
77
+ multiplyRatios(penaltyRate, quoteAsRatio(oraclePriceAtStart)),
78
+ );
79
+
80
+ const debtPortion = makeRatioFromAmounts(totalPenalty, totalDebt);
81
+
82
+ // We mutate the plan so that at any point if there's an error the plan can be returned and executed.
83
+ // IOW the plan must always be in a valid state, though perhaps incomplete.
84
+ /** @type {DistributionPlan} */
85
+ const plan = {
86
+ overage: accounting.overage,
87
+ shortfallToReserve: accounting.shortfall,
88
+ collateralForReserve: emptyCollateral,
89
+ actualCollateralSold: emptyCollateral,
90
+ collateralSold,
91
+ collatRemaining: emptyCollateral,
92
+ debtToBurn: emptyMinted,
93
+ mintedForReserve: emptyMinted,
94
+ mintedProceeds,
95
+ phantomDebt: emptyMinted,
96
+ transfersToVault: [],
97
+ vaultsToReinstate: [],
98
+ totalPenalty,
99
+ };
100
+
101
+ /**
102
+ * If interest was charged between liquidating and liquidated, erase it.
103
+ *
104
+ * @param {VaultBalances} balances
105
+ */
106
+ const updatePhantomDebt = ({ presaleDebt, currentDebt }) => {
107
+ if (AmountMath.isEqual(presaleDebt, currentDebt)) {
108
+ return;
109
+ }
110
+ const accrued = AmountMath.subtract(currentDebt, presaleDebt);
111
+ plan.phantomDebt = AmountMath.add(plan.phantomDebt, accrued);
112
+ };
113
+
114
+ const runFlow1 = () => {
115
+ // Flow #1: no shortfall
116
+
117
+ const distributableCollateral = subtractToEmpty(
118
+ collateralProceeds,
119
+ totalPenalty,
120
+ );
121
+
122
+ plan.debtToBurn = totalDebt;
123
+ plan.mintedForReserve = accounting.overage;
124
+
125
+ // return remaining funds to vaults before closing
126
+
127
+ let leftToStage = distributableCollateral;
128
+ const price = makeRatioFromAmounts(mintedProceeds, collateralSold);
129
+
130
+ // iterate from best to worst, returning collateral until it has
131
+ // been exhausted. Vaults after that get nothing.
132
+ for (const [vaultIndex, balances] of vaultsBalances.entries()) {
133
+ const { collateral: vCollat, presaleDebt } = balances;
134
+ updatePhantomDebt(balances);
135
+
136
+ // max return is vault value reduced by debt and penalty value
137
+ const debtCollat = ceilDivideBy(presaleDebt, price);
138
+ const penaltyCollat = ceilMultiplyBy(presaleDebt, debtPortion);
139
+ const lessCollat = AmountMath.add(debtCollat, penaltyCollat);
140
+
141
+ const maxCollat = subtractToEmpty(vCollat, lessCollat);
142
+ if (!AmountMath.isEmpty(leftToStage)) {
143
+ const collatReturn = AmountMath.min(leftToStage, maxCollat);
144
+ leftToStage = AmountMath.subtract(leftToStage, collatReturn);
145
+ plan.transfersToVault.push([vaultIndex, { Collateral: collatReturn }]);
146
+ }
147
+ }
148
+
149
+ const hasCollateralToDistribute = !AmountMath.isEmpty(
150
+ distributableCollateral,
151
+ );
152
+ plan.collateralForReserve = hasCollateralToDistribute
153
+ ? AmountMath.add(leftToStage, totalPenalty)
154
+ : collateralProceeds;
155
+ };
156
+
157
+ // Flow 2a: all collateral was sold but debt was not covered
158
+ const runFlow2a = () => {
159
+ // charge penalty if proceeds are sufficient
160
+ const penaltyInMinted = ceilMultiplyBy(totalDebt, penaltyRate);
161
+ const recoveredDebt = AmountMath.min(
162
+ AmountMath.add(totalDebt, penaltyInMinted),
163
+ mintedProceeds,
164
+ );
165
+
166
+ plan.debtToBurn = recoveredDebt;
167
+
168
+ const distributable = subtractToEmpty(mintedProceeds, recoveredDebt);
169
+ let mintedRemaining = distributable;
170
+
171
+ const vaultPortion = makeRatioFromAmounts(distributable, totalCollateral);
172
+
173
+ // iterate from best to worst returning remaining funds to vaults
174
+ for (const [vaultIndex, balances] of vaultsBalances.entries()) {
175
+ // from best to worst, return minted above penalty if any remains
176
+ const vaultShare = floorMultiplyBy(balances.collateral, vaultPortion);
177
+ updatePhantomDebt(balances);
178
+
179
+ if (!AmountMath.isEmpty(mintedRemaining)) {
180
+ const mintedToReturn = AmountMath.min(mintedRemaining, vaultShare);
181
+ mintedRemaining = AmountMath.subtract(mintedRemaining, mintedToReturn);
182
+ plan.transfersToVault.push([vaultIndex, { Minted: mintedToReturn }]);
183
+ }
184
+ }
185
+ };
186
+
187
+ // Flow 2b: collateral remains but debt was not covered
188
+ const runFlow2b = () => {
189
+ plan.debtToBurn = totalDebt;
190
+ plan.mintedForReserve = accounting.overage;
191
+
192
+ // reconstitute vaults until collateral is insufficient
193
+ let reconstituteVaults = AmountMath.isGTE(collateralProceeds, totalPenalty);
194
+
195
+ // charge penalty if proceeds are sufficient
196
+ const distributableCollateral = subtractToEmpty(
197
+ collateralProceeds,
198
+ totalPenalty,
199
+ );
200
+
201
+ plan.collatRemaining = distributableCollateral;
202
+
203
+ let shortfallToReserve = accounting.shortfall;
204
+ const reduceCollateral = amount =>
205
+ (plan.actualCollateralSold = AmountMath.add(
206
+ plan.actualCollateralSold,
207
+ amount,
208
+ ));
209
+
210
+ // iterate from best to worst attempting to reconstitute, by
211
+ // returning remaining funds to vaults
212
+ for (const [vaultIndex, balances] of vaultsBalances.entries()) {
213
+ const { collateral: vCollat, presaleDebt } = balances;
214
+
215
+ // according to #7123, Collateral for penalty =
216
+ // total liquidation penalty * vault debt / total debt
217
+ const vaultPenalty = ceilMultiplyBy(presaleDebt, debtPortion);
218
+ const collatPostPenalty = subtractToEmpty(vCollat, vaultPenalty);
219
+ const vaultDebt = floorMultiplyBy(presaleDebt, debtPortion);
220
+
221
+ // Should we continue reconstituting vaults?
222
+ reconstituteVaults =
223
+ reconstituteVaults &&
224
+ !AmountMath.isEmpty(collatPostPenalty) &&
225
+ AmountMath.isGTE(plan.collatRemaining, collatPostPenalty) &&
226
+ AmountMath.isGTE(totalDebt, presaleDebt);
227
+
228
+ if (reconstituteVaults) {
229
+ plan.collatRemaining = AmountMath.subtract(
230
+ plan.collatRemaining,
231
+ collatPostPenalty,
232
+ );
233
+ shortfallToReserve = subtractToEmpty(shortfallToReserve, presaleDebt);
234
+ // must reinstate after atomicRearrange(), so we record them.
235
+ plan.vaultsToReinstate.push(vaultIndex);
236
+ reduceCollateral(vaultDebt);
237
+ plan.transfersToVault.push([
238
+ vaultIndex,
239
+ { Collateral: collatPostPenalty },
240
+ ]);
241
+ } else {
242
+ updatePhantomDebt(balances);
243
+
244
+ reduceCollateral(vCollat);
245
+ }
246
+ }
247
+
248
+ plan.collateralForReserve = AmountMath.add(
249
+ plan.collatRemaining,
250
+ totalPenalty,
251
+ );
252
+
253
+ plan.shortfallToReserve = shortfallToReserve;
254
+ };
255
+
256
+ try {
257
+ if (AmountMath.isEmpty(accounting.shortfall)) {
258
+ // Flow #1: no shortfall
259
+ runFlow1();
260
+ } else if (AmountMath.isEmpty(collateralProceeds)) {
261
+ // Flow 2a: all collateral was sold but debt was not covered
262
+ runFlow2a();
263
+ } else {
264
+ // Flow #2b: There's unsold collateral; some vaults may be reinstated.
265
+ runFlow2b();
266
+ }
267
+ } catch (err) {
268
+ console.error('🚨 Failure running distribution flow:', err);
269
+ // continue to return `plan` in the state that was reached
270
+ }
271
+
272
+ return harden(plan);
273
+ };
274
+ harden(calculateDistributionPlan);
@@ -21,15 +21,6 @@
21
21
  * @property {() => Invitation} makeSwapInvitation
22
22
  */
23
23
 
24
- /**
25
- * @typedef {object} Collateral
26
- * @property {Ratio} liquidationMargin
27
- * @property {Ratio} stabilityFee
28
- * @property {Ratio} marketPrice
29
- * @property {Ratio} interestRate
30
- * @property {Brand} brand
31
- */
32
-
33
24
  /**
34
25
  * @typedef {object} VaultManagerParamValues
35
26
  * @property {Ratio} liquidationMargin - margin below which collateral will be
@@ -108,7 +99,7 @@
108
99
  * @typedef {object} LiquidationStrategy
109
100
  * @property {() => KeywordKeywordRecord} keywordMapping
110
101
  * @property {(collateral: Amount, run: Amount) => Proposal} makeProposal
111
- * @property {(runDebt: Amount) => Promise<Invitation>} makeInvitation
102
+ * @property {(debt: Amount) => Promise<Invitation>} makeInvitation
112
103
  */
113
104
 
114
105
  /**