@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agoric/inter-protocol",
3
- "version": "0.14.1-dev-effbf7f.0+effbf7f",
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-effbf7f.0+effbf7f",
33
- "@agoric/ertp": "0.16.1-dev-effbf7f.0+effbf7f",
34
- "@agoric/governance": "0.10.1-dev-effbf7f.0+effbf7f",
35
- "@agoric/internal": "0.3.1-dev-effbf7f.0+effbf7f",
36
- "@agoric/notifier": "0.6.1-dev-effbf7f.0+effbf7f",
37
- "@agoric/store": "0.9.1-dev-effbf7f.0+effbf7f",
38
- "@agoric/time": "0.3.1-dev-effbf7f.0+effbf7f",
39
- "@agoric/vat-data": "0.5.1-dev-effbf7f.0+effbf7f",
40
- "@agoric/vats": "0.14.1-dev-effbf7f.0+effbf7f",
41
- "@agoric/zoe": "0.26.1-dev-effbf7f.0+effbf7f",
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-effbf7f.0+effbf7f",
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-effbf7f.0+effbf7f",
52
- "@agoric/smart-wallet": "0.5.1-dev-effbf7f.0+effbf7f",
53
- "@agoric/swingset-liveslots": "0.10.1-dev-effbf7f.0+effbf7f",
54
- "@agoric/swingset-vat": "0.32.1-dev-effbf7f.0+effbf7f",
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": "effbf7f7a278863b88419e0a19ae19f0db4b30a0"
82
+ "gitHead": "72b0aa9ce03797b76d8d671b010d1a1615b5ae45"
83
83
  }
@@ -2,6 +2,7 @@
2
2
 
3
3
  import { AmountMath } from '@agoric/ertp';
4
4
  import { M } from '@agoric/store';
5
+ import { makeRatioFromAmounts } from '@agoric/zoe/src/contractSupport/index.js';
5
6
 
6
7
  const { Fail, quote: q } = assert;
7
8
 
@@ -27,6 +28,17 @@ export const ratioPattern = harden({
27
28
  export const addSubtract = (base, gain, loss) =>
28
29
  AmountMath.subtract(AmountMath.add(base, gain), loss);
29
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
+
30
42
  /**
31
43
  * Verifies that every key in the proposal is in the provided list
32
44
  *
@@ -83,3 +95,7 @@ export const checkDebtLimit = (debtLimit, totalDebt, toMint) => {
83
95
  */
84
96
  export const makeNatAmountShape = (brand, min) =>
85
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);
@@ -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);
@@ -84,7 +84,7 @@ const shortfallInvitationKey = 'shortfallInvitation';
84
84
  * @param {import('@agoric/zoe/src/contractSupport/recorder.js').MakeRecorderKit} makeRecorderKit
85
85
  * @param {import('@agoric/zoe/src/contractSupport/recorder.js').MakeERecorderKit} makeERecorderKit
86
86
  */
87
- export const prepareVaultDirector = (
87
+ const prepareVaultDirector = (
88
88
  baggage,
89
89
  zcf,
90
90
  directorParamManager,
@@ -13,9 +13,9 @@ import '@agoric/zoe/src/contracts/exported.js';
13
13
 
14
14
  // This contract wants to be managed by a contractGovernor, but it isn't
15
15
  // compatible with contractGovernor, since it has a separate paramManager for
16
- // each Vault. This requires it to manually replicate the API of contractHelper
17
- // to satisfy contractGovernor. It needs to return a creatorFacet with
18
- // { getParamMgrRetriever, getInvitation, getLimitedCreatorFacet }.
16
+ // each VaultManager. This requires it to manually replicate the API of
17
+ // contractHelper to satisfy contractGovernor. It needs to return a creatorFacet
18
+ // with { getParamMgrRetriever, getInvitation, getLimitedCreatorFacet }.
19
19
 
20
20
  import { CONTRACT_ELECTORATE } from '@agoric/governance';
21
21
  import { makeParamManagerFromTerms } from '@agoric/governance/src/contractGovernance/typedParamManager.js';