@agoric/inter-protocol 0.13.4-pismo-dev-0d5327f.0 → 0.14.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.
Files changed (141) hide show
  1. package/CHANGELOG.md +287 -0
  2. package/README.md +26 -20
  3. package/bundles/bundle-auctioneer-js-meta.json +722 -0
  4. package/bundles/bundle-auctioneer.js +1 -0
  5. package/bundles/bundle-econCommitteeCharter-js-meta.json +514 -0
  6. package/bundles/bundle-econCommitteeCharter.js +1 -0
  7. package/bundles/bundle-feeDistributor-js-meta.json +458 -0
  8. package/bundles/bundle-feeDistributor.js +1 -1
  9. package/bundles/bundle-fluxAggregatorKit-js-meta.json +674 -0
  10. package/bundles/bundle-fluxAggregatorKit.js +1 -0
  11. package/bundles/bundle-psm-js-meta.json +666 -0
  12. package/bundles/bundle-psm.js +1 -1
  13. package/bundles/bundle-reserve-js-meta.json +662 -0
  14. package/bundles/bundle-reserve.js +1 -1
  15. package/bundles/bundle-scaledPriceAuthority-js-meta.json +542 -0
  16. package/bundles/bundle-scaledPriceAuthority.js +1 -1
  17. package/bundles/bundle-vaultFactory-js-meta.json +786 -0
  18. package/bundles/bundle-vaultFactory.js +1 -1
  19. package/exported.js +0 -2
  20. package/package.json +34 -31
  21. package/scripts/add-collateral-core.js +14 -4
  22. package/scripts/build-bundles.js +9 -2
  23. package/scripts/deploy-contracts.js +1 -4
  24. package/scripts/init-core.js +20 -38
  25. package/scripts/invite-committee-core.js +1 -1
  26. package/scripts/manual-price-feed.js +2 -2
  27. package/scripts/price-feed-core.js +7 -6
  28. package/scripts/start-local-chain.sh +84 -0
  29. package/src/auction/auctionBook.js +755 -0
  30. package/src/auction/auctioneer.js +720 -0
  31. package/src/auction/offerBook.js +221 -0
  32. package/src/auction/params.js +174 -0
  33. package/src/auction/scheduleMath.js +167 -0
  34. package/src/auction/scheduler.js +370 -0
  35. package/src/auction/sortedOffers.js +136 -0
  36. package/src/auction/util.js +55 -0
  37. package/src/clientSupport.js +324 -0
  38. package/src/collectFees.js +8 -14
  39. package/src/contractSupport.js +24 -68
  40. package/src/econCommitteeCharter.js +161 -77
  41. package/src/feeDistributor.js +60 -20
  42. package/src/index.js +2 -0
  43. package/src/interest-math.js +1 -4
  44. package/src/interest.js +20 -13
  45. package/src/price/README.md +57 -0
  46. package/src/price/fluxAggregatorContract.js +190 -0
  47. package/src/price/fluxAggregatorKit.js +375 -0
  48. package/src/price/priceOracleKit.js +127 -0
  49. package/src/price/roundsManager.js +726 -0
  50. package/src/proposals/addAssetToVault.js +89 -94
  51. package/src/proposals/committee-proposal.js +141 -40
  52. package/src/proposals/core-proposal.js +100 -167
  53. package/src/proposals/econ-behaviors.js +320 -564
  54. package/src/proposals/price-feed-proposal.js +175 -78
  55. package/src/proposals/startEconCommittee.js +25 -12
  56. package/src/proposals/startPSM.js +231 -211
  57. package/src/proposals/utils.js +31 -17
  58. package/src/psm/README.md +71 -0
  59. package/src/psm/psm.js +272 -160
  60. package/src/psm/types.js +2 -0
  61. package/src/reserve/assetReserve.js +56 -413
  62. package/src/reserve/assetReserveKit.js +303 -0
  63. package/src/reserve/doc.md +1 -3
  64. package/src/reserve/params.js +2 -5
  65. package/src/typeGuards.js +13 -0
  66. package/src/vaultFactory/Liquidation.md +100 -0
  67. package/src/vaultFactory/burn.js +2 -1
  68. package/src/vaultFactory/liquidation.js +270 -108
  69. package/src/vaultFactory/math.js +95 -0
  70. package/src/vaultFactory/orderedVaultStore.js +0 -1
  71. package/src/vaultFactory/params.js +133 -112
  72. package/src/vaultFactory/prioritizedVaults.js +36 -79
  73. package/src/vaultFactory/storeUtils.js +67 -9
  74. package/src/vaultFactory/type-imports.js +2 -0
  75. package/src/vaultFactory/types.js +22 -26
  76. package/src/vaultFactory/vault.js +772 -709
  77. package/src/vaultFactory/vaultDirector.js +480 -483
  78. package/src/vaultFactory/vaultFactory.js +99 -60
  79. package/src/vaultFactory/vaultHolder.js +121 -95
  80. package/src/vaultFactory/vaultKit.js +39 -31
  81. package/src/vaultFactory/vaultManager.js +1249 -868
  82. package/bundles/bundle-amm-meta.js +0 -618
  83. package/bundles/bundle-amm.js +0 -1
  84. package/bundles/bundle-feeDistributor-meta.js +0 -346
  85. package/bundles/bundle-interchainPool-meta.js +0 -534
  86. package/bundles/bundle-interchainPool.js +0 -1
  87. package/bundles/bundle-liquidateIncrementally-meta.js +0 -442
  88. package/bundles/bundle-liquidateIncrementally.js +0 -1
  89. package/bundles/bundle-liquidateMinimum-meta.js +0 -442
  90. package/bundles/bundle-liquidateMinimum.js +0 -1
  91. package/bundles/bundle-psm-meta.js +0 -538
  92. package/bundles/bundle-psmCharter-meta.js +0 -418
  93. package/bundles/bundle-psmCharter.js +0 -1
  94. package/bundles/bundle-reserve-meta.js +0 -542
  95. package/bundles/bundle-scaledPriceAuthority-meta.js +0 -442
  96. package/bundles/bundle-stakeFactory-meta.js +0 -574
  97. package/bundles/bundle-stakeFactory.js +0 -1
  98. package/bundles/bundle-vaultFactory-meta.js +0 -598
  99. package/scripts/add-initial-liquidity.js +0 -180
  100. package/src/collect.js +0 -13
  101. package/src/interchainPool.js +0 -135
  102. package/src/makeTracer.js +0 -36
  103. package/src/my-lien.js +0 -85
  104. package/src/proposals/demoIssuers.js +0 -685
  105. package/src/proposals/sim-behaviors.js +0 -40
  106. package/src/psm/psmCharter.js +0 -139
  107. package/src/stakeFactory/README.md +0 -62
  108. package/src/stakeFactory/attestation.js +0 -267
  109. package/src/stakeFactory/attestationTool.js +0 -66
  110. package/src/stakeFactory/constants.js +0 -16
  111. package/src/stakeFactory/params.js +0 -31
  112. package/src/stakeFactory/stakeFactory.js +0 -236
  113. package/src/stakeFactory/stakeFactoryKit.js +0 -455
  114. package/src/stakeFactory/stakeFactoryManager.js +0 -296
  115. package/src/stakeFactory/types.js +0 -42
  116. package/src/vaultFactory/liquidateIncrementally.js +0 -350
  117. package/src/vaultFactory/liquidateMinimum.js +0 -100
  118. package/src/vpool-xyk-amm/AttackersGuide.md +0 -89
  119. package/src/vpool-xyk-amm/addLiquidity.js +0 -222
  120. package/src/vpool-xyk-amm/addPool.js +0 -197
  121. package/src/vpool-xyk-amm/constantProduct/AMM-Trade.jpeg +0 -0
  122. package/src/vpool-xyk-amm/constantProduct/README.md +0 -204
  123. package/src/vpool-xyk-amm/constantProduct/calcFees.js +0 -99
  124. package/src/vpool-xyk-amm/constantProduct/calcSwapPrices.js +0 -85
  125. package/src/vpool-xyk-amm/constantProduct/core.js +0 -153
  126. package/src/vpool-xyk-amm/constantProduct/defaults.js +0 -5
  127. package/src/vpool-xyk-amm/constantProduct/getXY.js +0 -52
  128. package/src/vpool-xyk-amm/constantProduct/internal-types.js +0 -116
  129. package/src/vpool-xyk-amm/constantProduct/invariants.js +0 -43
  130. package/src/vpool-xyk-amm/constantProduct/swap.js +0 -272
  131. package/src/vpool-xyk-amm/constantProduct/types.js +0 -32
  132. package/src/vpool-xyk-amm/doublePool.js +0 -194
  133. package/src/vpool-xyk-amm/estimate.js +0 -155
  134. package/src/vpool-xyk-amm/multipoolMarketMaker.js +0 -520
  135. package/src/vpool-xyk-amm/params.js +0 -61
  136. package/src/vpool-xyk-amm/pool.js +0 -396
  137. package/src/vpool-xyk-amm/priceAuthority.js +0 -94
  138. package/src/vpool-xyk-amm/removeLiquidity.js +0 -31
  139. package/src/vpool-xyk-amm/singlePool.js +0 -73
  140. package/src/vpool-xyk-amm/swap.js +0 -69
  141. package/src/vpool-xyk-amm/types.js +0 -154
@@ -0,0 +1,755 @@
1
+ import '@agoric/governance/exported.js';
2
+ import '@agoric/zoe/exported.js';
3
+ import '@agoric/zoe/src/contracts/exported.js';
4
+
5
+ import { AmountMath } from '@agoric/ertp';
6
+ import { mustMatch } from '@agoric/store';
7
+ import { M, prepareExoClassKit } from '@agoric/vat-data';
8
+
9
+ import { assertAllDefined, makeTracer } from '@agoric/internal';
10
+ import {
11
+ atomicRearrange,
12
+ ceilMultiplyBy,
13
+ floorDivideBy,
14
+ makeRatioFromAmounts,
15
+ makeRecorderTopic,
16
+ multiplyRatios,
17
+ ratioGTE,
18
+ } from '@agoric/zoe/src/contractSupport/index.js';
19
+ import { E } from '@endo/captp';
20
+ import { observeNotifier } from '@agoric/notifier';
21
+
22
+ import { makeNatAmountShape } from '../contractSupport.js';
23
+ import { preparePriceBook, prepareScaledBidBook } from './offerBook.js';
24
+ import {
25
+ isScaledBidPriceHigher,
26
+ makeBrandedRatioPattern,
27
+ priceFrom,
28
+ } from './util.js';
29
+
30
+ const { Fail } = assert;
31
+ const { makeEmpty } = AmountMath;
32
+
33
+ const DEFAULT_DECIMALS = 9;
34
+
35
+ /**
36
+ * @file The book represents the collateral-specific state of an ongoing
37
+ * auction. It holds the book, the capturedPrice, and the collateralSeat that has
38
+ * the allocation of assets for sale.
39
+ *
40
+ * The book contains orders for the collateral. It holds two kinds of
41
+ * orders:
42
+ * - Prices express the bidding offer in terms of a Bid amount
43
+ * - ScaledBids express the offer in terms of a discount (or markup) from the
44
+ * most recent oracle price.
45
+ *
46
+ * Offers can be added in three ways. 1) When the auction is not active, prices
47
+ * are automatically added to the appropriate collection. When the auction is
48
+ * active, 2) if a new offer is at or above the current price, it will be
49
+ * settled immediately; 2) If the offer is below the current price, it will be
50
+ * added in the appropriate place and settled when the price reaches that level.
51
+ */
52
+
53
+ const trace = makeTracer('AucBook', true);
54
+
55
+ /**
56
+ * @typedef {{
57
+ * maxBuy: Amount<'nat'>
58
+ * } & {
59
+ * exitAfterBuy?: boolean,
60
+ * } & ({
61
+ * offerPrice: Ratio,
62
+ * } | {
63
+ * offerBidScaling: Ratio,
64
+ * })} OfferSpec
65
+ */
66
+ /**
67
+ * @param {Brand<'nat'>} bidBrand
68
+ * @param {Brand<'nat'>} collateralBrand
69
+ */
70
+ export const makeOfferSpecShape = (bidBrand, collateralBrand) => {
71
+ const bidAmountShape = makeNatAmountShape(bidBrand);
72
+ const collateralAmountShape = makeNatAmountShape(collateralBrand);
73
+ return M.splitRecord(
74
+ { maxBuy: collateralAmountShape },
75
+ {
76
+ exitAfterBuy: M.boolean(),
77
+ // xxx should have exactly one of these properties
78
+ offerPrice: makeBrandedRatioPattern(
79
+ bidAmountShape,
80
+ collateralAmountShape,
81
+ ),
82
+ offerBidScaling: makeBrandedRatioPattern(bidAmountShape, bidAmountShape),
83
+ },
84
+ );
85
+ };
86
+
87
+ /** @typedef {import('@agoric/vat-data').Baggage} Baggage */
88
+
89
+ /**
90
+ * @typedef {object} BookDataNotification
91
+ *
92
+ * @property {Ratio | null} startPrice identifies the priceAuthority and price
93
+ * @property {Ratio | null} currentPriceLevel the price at the current auction tier
94
+ * @property {Amount<'nat'> | null} startProceedsGoal The proceeds the sellers were targeting to raise
95
+ * @property {Amount<'nat'> | null} remainingProceedsGoal The remainder of
96
+ * the proceeds the sellers were targeting to raise
97
+ * @property {Amount<'nat'> | undefined} proceedsRaised The proceeds raised so far in the auction
98
+ * @property {Amount<'nat'>} startCollateral How much collateral was
99
+ * available for sale at the start. (If more is deposited later, it'll be
100
+ * added in.)
101
+ * @property {Amount<'nat'> | null} collateralAvailable The amount of collateral remaining
102
+ */
103
+
104
+ /**
105
+ * @param {Baggage} baggage
106
+ * @param {ZCF} zcf
107
+ * @param {import('@agoric/zoe/src/contractSupport/recorder.js').MakeRecorderKit} makeRecorderKit
108
+ */
109
+ export const prepareAuctionBook = (baggage, zcf, makeRecorderKit) => {
110
+ const makeScaledBidBook = prepareScaledBidBook(baggage);
111
+ const makePriceBook = preparePriceBook(baggage);
112
+
113
+ const AuctionBookStateShape = harden({
114
+ collateralBrand: M.any(),
115
+ collateralSeat: M.any(),
116
+ collateralAmountShape: M.any(),
117
+ bidBrand: M.any(),
118
+ bidHoldingSeat: M.any(),
119
+ bidAmountShape: M.any(),
120
+ priceAuthority: M.any(),
121
+ updatingOracleQuote: M.any(),
122
+ bookDataKit: M.any(),
123
+ priceBook: M.any(),
124
+ scaledBidBook: M.any(),
125
+ startCollateral: M.any(),
126
+ startProceedsGoal: M.any(),
127
+ capturedPriceForRound: M.any(),
128
+ curAuctionPrice: M.any(),
129
+ remainingProceedsGoal: M.any(),
130
+ });
131
+
132
+ const makeAuctionBookKit = prepareExoClassKit(
133
+ baggage,
134
+ 'AuctionBook',
135
+ undefined,
136
+ /**
137
+ * @param {Brand<'nat'>} bidBrand
138
+ * @param {Brand<'nat'>} collateralBrand
139
+ * @param {PriceAuthority} pAuthority
140
+ * @param {StorageNode} node
141
+ */
142
+ (bidBrand, collateralBrand, pAuthority, node) => {
143
+ assertAllDefined({ bidBrand, collateralBrand, pAuthority });
144
+ const zeroBid = makeEmpty(bidBrand);
145
+ const zeroRatio = makeRatioFromAmounts(
146
+ zeroBid,
147
+ AmountMath.make(collateralBrand, 1n),
148
+ );
149
+
150
+ // these don't have to be durable, since we're currently assuming that upgrade
151
+ // from a quiescent state is sufficient. When the auction is quiescent, there
152
+ // may be offers in the book, but these seats will be empty, with all assets
153
+ // returned to the funders.
154
+ const { zcfSeat: collateralSeat } = zcf.makeEmptySeatKit();
155
+ const { zcfSeat: bidHoldingSeat } = zcf.makeEmptySeatKit();
156
+
157
+ const bidAmountShape = makeNatAmountShape(bidBrand);
158
+ const collateralAmountShape = makeNatAmountShape(collateralBrand);
159
+ const scaledBidBook = makeScaledBidBook(
160
+ makeBrandedRatioPattern(bidAmountShape, bidAmountShape),
161
+ collateralBrand,
162
+ );
163
+
164
+ const priceBook = makePriceBook(
165
+ makeBrandedRatioPattern(bidAmountShape, collateralAmountShape),
166
+ collateralBrand,
167
+ );
168
+
169
+ const bookDataKit = makeRecorderKit(
170
+ node,
171
+ /** @type {import('@agoric/zoe/src/contractSupport/recorder.js').TypedMatcher<BookDataNotification>} */ (
172
+ M.any()
173
+ ),
174
+ );
175
+
176
+ return {
177
+ collateralBrand,
178
+ collateralSeat,
179
+ collateralAmountShape,
180
+ bidBrand,
181
+ bidHoldingSeat,
182
+ bidAmountShape,
183
+
184
+ priceAuthority: pAuthority,
185
+ updatingOracleQuote: zeroRatio,
186
+
187
+ bookDataKit,
188
+
189
+ priceBook,
190
+ scaledBidBook,
191
+ /**
192
+ * Set to empty at the end of an auction. It increases when
193
+ * `addAssets()` is called
194
+ */
195
+ startCollateral: AmountMath.makeEmpty(collateralBrand),
196
+
197
+ /**
198
+ * Null indicates no limit; empty indicates limit exhausted. It is reset
199
+ * at the end of each auction. It increases when `addAssets()` is called
200
+ * with a goal.
201
+ *
202
+ * @type {Amount<'nat'> | null}
203
+ */
204
+ startProceedsGoal: null,
205
+
206
+ /**
207
+ * Assigned a value to capture the price and reset to null at the end of
208
+ * each auction.
209
+ *
210
+ * @type {Ratio | null}
211
+ */
212
+ capturedPriceForRound: null,
213
+
214
+ /**
215
+ * non-null during auctions. It is assigned a value at the beginning of
216
+ * each descending step, and reset at the end of the auction.
217
+ *
218
+ * @type {Ratio | null}
219
+ */
220
+ curAuctionPrice: null,
221
+
222
+ /**
223
+ * null outside of auctions. during an auction null indicates no limit;
224
+ * empty indicates limit exhausted
225
+ *
226
+ * @type {Amount<'nat'> | null}
227
+ */
228
+ remainingProceedsGoal: null,
229
+ };
230
+ },
231
+ {
232
+ helper: {
233
+ /**
234
+ * remove the key from the appropriate book, indicated by whether the price
235
+ * is defined.
236
+ *
237
+ * @param {string} key
238
+ * @param {Ratio | undefined} price
239
+ */
240
+ removeFromItsBook(key, price) {
241
+ const { priceBook, scaledBidBook } = this.state;
242
+ if (price) {
243
+ priceBook.delete(key);
244
+ } else {
245
+ scaledBidBook.delete(key);
246
+ }
247
+ },
248
+
249
+ /**
250
+ * Update the entry in the appropriate book, indicated by whether the price
251
+ * is defined.
252
+ *
253
+ * @param {string} key
254
+ * @param {Amount} collateralSold
255
+ * @param {Ratio | undefined} price
256
+ */
257
+ updateItsBook(key, collateralSold, price) {
258
+ const { priceBook, scaledBidBook } = this.state;
259
+ if (price) {
260
+ priceBook.updateReceived(key, collateralSold);
261
+ } else {
262
+ scaledBidBook.updateReceived(key, collateralSold);
263
+ }
264
+ },
265
+
266
+ /**
267
+ * Settle with seat. The caller is responsible for updating the book, if any.
268
+ *
269
+ * @param {ZCFSeat} seat
270
+ * @param {Amount<'nat'>} collateralWanted
271
+ */
272
+ settle(seat, collateralWanted) {
273
+ const { collateralSeat, collateralBrand } = this.state;
274
+ const { Bid: bidAlloc } = seat.getCurrentAllocation();
275
+ const { Collateral: collateralAvailable } =
276
+ collateralSeat.getCurrentAllocation();
277
+ if (!collateralAvailable || AmountMath.isEmpty(collateralAvailable)) {
278
+ return makeEmpty(collateralBrand);
279
+ }
280
+
281
+ /** @type {Amount<'nat'>} */
282
+ const initialCollateralTarget = AmountMath.min(
283
+ collateralWanted,
284
+ collateralAvailable,
285
+ );
286
+
287
+ const { curAuctionPrice, bidHoldingSeat, remainingProceedsGoal } =
288
+ this.state;
289
+ curAuctionPrice !== null ||
290
+ Fail`auctionPrice must be set before each round`;
291
+ assert(curAuctionPrice);
292
+
293
+ const proceedsNeeded = ceilMultiplyBy(
294
+ initialCollateralTarget,
295
+ curAuctionPrice,
296
+ );
297
+ if (AmountMath.isEmpty(proceedsNeeded)) {
298
+ seat.fail(Error('price fell to zero'));
299
+ return makeEmpty(collateralBrand);
300
+ }
301
+
302
+ const minProceedsTarget = AmountMath.min(proceedsNeeded, bidAlloc);
303
+ const proceedsLimit = remainingProceedsGoal
304
+ ? AmountMath.min(remainingProceedsGoal, minProceedsTarget)
305
+ : minProceedsTarget;
306
+ const isRaiseLimited =
307
+ remainingProceedsGoal ||
308
+ !AmountMath.isGTE(proceedsLimit, proceedsNeeded);
309
+
310
+ const [proceedsTarget, collateralTarget] = isRaiseLimited
311
+ ? [proceedsLimit, floorDivideBy(proceedsLimit, curAuctionPrice)]
312
+ : [minProceedsTarget, initialCollateralTarget];
313
+
314
+ const { Collateral } = seat.getProposal().want;
315
+ if (Collateral && AmountMath.isGTE(Collateral, collateralTarget)) {
316
+ seat.exit('unable to satisfy want');
317
+ }
318
+
319
+ atomicRearrange(
320
+ zcf,
321
+ harden([
322
+ [collateralSeat, seat, { Collateral: collateralTarget }],
323
+ [seat, bidHoldingSeat, { Bid: proceedsTarget }],
324
+ ]),
325
+ );
326
+
327
+ if (remainingProceedsGoal) {
328
+ this.state.remainingProceedsGoal = AmountMath.subtract(
329
+ remainingProceedsGoal,
330
+ proceedsTarget,
331
+ );
332
+ }
333
+
334
+ trace('settle', {
335
+ collateralTarget,
336
+ proceedsTarget,
337
+ remainingProceedsGoal: this.state.remainingProceedsGoal,
338
+ });
339
+
340
+ return collateralTarget;
341
+ },
342
+
343
+ /**
344
+ * Accept an offer expressed as a price. If the auction is active, attempt to
345
+ * buy collateral. If any of the offer remains add it to the book.
346
+ *
347
+ * @param {ZCFSeat} seat
348
+ * @param {Ratio} price
349
+ * @param {Amount<'nat'>} maxBuy
350
+ * @param {object} opts
351
+ * @param {boolean} opts.trySettle
352
+ * @param {boolean} [opts.exitAfterBuy]
353
+ */
354
+ acceptPriceOffer(
355
+ seat,
356
+ price,
357
+ maxBuy,
358
+ { trySettle, exitAfterBuy = false },
359
+ ) {
360
+ const { priceBook, curAuctionPrice } = this.state;
361
+ const { helper } = this.facets;
362
+ trace(this.state.collateralBrand, 'acceptPrice');
363
+
364
+ const settleIfPriceExists = () => {
365
+ if (curAuctionPrice !== null) {
366
+ return trySettle && ratioGTE(price, curAuctionPrice)
367
+ ? helper.settle(seat, maxBuy)
368
+ : AmountMath.makeEmptyFromAmount(maxBuy);
369
+ } else {
370
+ return AmountMath.makeEmptyFromAmount(maxBuy);
371
+ }
372
+ };
373
+ const collateralSold = settleIfPriceExists();
374
+
375
+ const stillWant = AmountMath.subtract(maxBuy, collateralSold);
376
+ if (
377
+ (exitAfterBuy && !AmountMath.isEmpty(collateralSold)) ||
378
+ AmountMath.isEmpty(stillWant) ||
379
+ AmountMath.isEmpty(seat.getCurrentAllocation().Bid)
380
+ ) {
381
+ seat.exit();
382
+ } else {
383
+ trace('added Offer ', price, stillWant.value);
384
+ priceBook.add(seat, price, stillWant, exitAfterBuy);
385
+ }
386
+
387
+ void helper.publishBookData();
388
+ },
389
+
390
+ /**
391
+ * Accept an offer expressed as a discount (or markup). If the auction is
392
+ * active, attempt to buy collateral. If any of the offer remains add it to
393
+ * the book.
394
+ *
395
+ * @param {ZCFSeat} seat
396
+ * @param {Ratio} bidScaling
397
+ * @param {Amount<'nat'>} maxBuy
398
+ * @param {object} opts
399
+ * @param {boolean} opts.trySettle
400
+ * @param {boolean} [opts.exitAfterBuy]
401
+ */
402
+ acceptScaledBidOffer(
403
+ seat,
404
+ bidScaling,
405
+ maxBuy,
406
+ { trySettle, exitAfterBuy = false },
407
+ ) {
408
+ trace(this.state.collateralBrand, 'accept scaledBid offer');
409
+ const { curAuctionPrice, capturedPriceForRound, scaledBidBook } =
410
+ this.state;
411
+ const { helper } = this.facets;
412
+
413
+ const settleIfPricesDefined = () => {
414
+ if (
415
+ curAuctionPrice &&
416
+ capturedPriceForRound &&
417
+ trySettle &&
418
+ isScaledBidPriceHigher(
419
+ bidScaling,
420
+ curAuctionPrice,
421
+ capturedPriceForRound,
422
+ )
423
+ ) {
424
+ return helper.settle(seat, maxBuy);
425
+ }
426
+ return AmountMath.makeEmptyFromAmount(maxBuy);
427
+ };
428
+ const collateralSold = settleIfPricesDefined();
429
+
430
+ const stillWant = AmountMath.subtract(maxBuy, collateralSold);
431
+ if (
432
+ (exitAfterBuy && !AmountMath.isEmpty(collateralSold)) ||
433
+ AmountMath.isEmpty(stillWant) ||
434
+ AmountMath.isEmpty(seat.getCurrentAllocation().Bid)
435
+ ) {
436
+ seat.exit();
437
+ } else {
438
+ scaledBidBook.add(seat, bidScaling, stillWant, exitAfterBuy);
439
+ }
440
+
441
+ void helper.publishBookData();
442
+ },
443
+ publishBookData() {
444
+ const { state } = this;
445
+
446
+ const allocation = state.collateralSeat.getCurrentAllocation();
447
+ const collateralAvailable =
448
+ 'Collateral' in allocation
449
+ ? allocation.Collateral
450
+ : makeEmpty(state.collateralBrand);
451
+
452
+ const bookData = harden({
453
+ startPrice: state.capturedPriceForRound,
454
+ startProceedsGoal: state.startProceedsGoal,
455
+ remainingProceedsGoal: state.remainingProceedsGoal,
456
+ proceedsRaised: allocation.Bid,
457
+ startCollateral: state.startCollateral,
458
+ collateralAvailable,
459
+ currentPriceLevel: state.curAuctionPrice,
460
+ });
461
+ return state.bookDataKit.recorder.write(bookData);
462
+ },
463
+ },
464
+ self: {
465
+ /**
466
+ * @param {Amount<'nat'>} assetAmount
467
+ * @param {ZCFSeat} sourceSeat
468
+ * @param {Amount<'nat'>} [proceedsGoal] an amount that the depositor
469
+ * would like to raise. The auction is requested to not sell more
470
+ * collateral than required to raise that much. The auctioneer might
471
+ * sell more if there is more than one supplier of collateral, and
472
+ * they request inconsistent limits.
473
+ */
474
+ addAssets(assetAmount, sourceSeat, proceedsGoal) {
475
+ const { state, facets } = this;
476
+ trace('add assets', { assetAmount, proceedsGoal });
477
+ const { collateralBrand, collateralSeat, startProceedsGoal } = state;
478
+
479
+ // When adding assets, the new ratio of totalCollectionGoal to collateral
480
+ // allocation will be the larger of the existing ratio and the ratio
481
+ // implied by the new deposit. Add the new collateral and raise
482
+ // startProceedsGoal so it's proportional to the new ratio. This can
483
+ // result in raising more Bid than one depositor wanted, but
484
+ // that's better than not selling as much as the other desired.
485
+
486
+ const allocation = collateralSeat.getCurrentAllocation();
487
+ const curCollateral =
488
+ 'Collateral' in allocation
489
+ ? allocation.Collateral
490
+ : makeEmpty(collateralBrand);
491
+
492
+ // when neither proceedsGoal nor startProceedsGoal is defined, we don't need an
493
+ // update and the call immediately below won't invoke this function.
494
+ const calcTargetRatio = () => {
495
+ if (startProceedsGoal && !proceedsGoal) {
496
+ return makeRatioFromAmounts(startProceedsGoal, curCollateral);
497
+ } else if (!startProceedsGoal && proceedsGoal) {
498
+ return makeRatioFromAmounts(proceedsGoal, assetAmount);
499
+ } else if (startProceedsGoal && proceedsGoal) {
500
+ const curRatio = makeRatioFromAmounts(
501
+ startProceedsGoal,
502
+ AmountMath.add(curCollateral, assetAmount),
503
+ );
504
+ const newRatio = makeRatioFromAmounts(proceedsGoal, assetAmount);
505
+ return ratioGTE(newRatio, curRatio) ? newRatio : curRatio;
506
+ }
507
+
508
+ throw Fail`calcTargetRatio called with !remainingProceedsGoal && !proceedsGoal`;
509
+ };
510
+
511
+ if (proceedsGoal || startProceedsGoal) {
512
+ const nextProceedsGoal = ceilMultiplyBy(
513
+ AmountMath.add(curCollateral, assetAmount),
514
+ calcTargetRatio(),
515
+ );
516
+
517
+ if (state.remainingProceedsGoal !== null) {
518
+ const incrementToGoal = state.startProceedsGoal
519
+ ? AmountMath.subtract(nextProceedsGoal, state.startProceedsGoal)
520
+ : nextProceedsGoal;
521
+
522
+ state.remainingProceedsGoal = state.remainingProceedsGoal
523
+ ? AmountMath.add(state.remainingProceedsGoal, incrementToGoal)
524
+ : incrementToGoal;
525
+ }
526
+
527
+ state.startProceedsGoal = nextProceedsGoal;
528
+ }
529
+
530
+ atomicRearrange(
531
+ zcf,
532
+ harden([[sourceSeat, collateralSeat, { Collateral: assetAmount }]]),
533
+ );
534
+
535
+ state.startCollateral = state.startCollateral
536
+ ? AmountMath.add(state.startCollateral, assetAmount)
537
+ : assetAmount;
538
+ void facets.helper.publishBookData();
539
+ },
540
+ /** @type {(reduction: Ratio) => void} */
541
+ settleAtNewRate(reduction) {
542
+ const { state, facets } = this;
543
+
544
+ trace(this.state.collateralBrand, 'settleAtNewRate', reduction);
545
+ const { capturedPriceForRound, priceBook, scaledBidBook } = state;
546
+ capturedPriceForRound !== null ||
547
+ Fail`price must be captured before auction starts`;
548
+ assert(capturedPriceForRound);
549
+
550
+ state.curAuctionPrice = multiplyRatios(
551
+ reduction,
552
+ capturedPriceForRound,
553
+ );
554
+ // extract after it's set in state
555
+ const { curAuctionPrice } = state;
556
+
557
+ const pricedOffers = priceBook.offersAbove(curAuctionPrice);
558
+ const scaledBidOffers = scaledBidBook.offersAbove(reduction);
559
+
560
+ const compareValues = (v1, v2) => {
561
+ if (v1 < v2) {
562
+ return -1;
563
+ } else if (v1 === v2) {
564
+ return 0;
565
+ } else {
566
+ return 1;
567
+ }
568
+ };
569
+ trace(`settling`, pricedOffers.length, scaledBidOffers.length);
570
+ // requested price or BidScaling gives no priority beyond specifying which
571
+ // round the order will be serviced in.
572
+ const prioritizedOffers = [...pricedOffers, ...scaledBidOffers].sort(
573
+ (a, b) => compareValues(a[1].seqNum, b[1].seqNum),
574
+ );
575
+
576
+ const { remainingProceedsGoal } = state;
577
+ const { helper } = facets;
578
+ for (const [key, seatRecord] of prioritizedOffers) {
579
+ const { seat, price: p, wanted, exitAfterBuy } = seatRecord;
580
+ if (
581
+ remainingProceedsGoal &&
582
+ AmountMath.isEmpty(remainingProceedsGoal)
583
+ ) {
584
+ break;
585
+ } else if (seat.hasExited()) {
586
+ helper.removeFromItsBook(key, p);
587
+ } else {
588
+ const collateralSold = helper.settle(seat, wanted);
589
+
590
+ const alloc = seat.getCurrentAllocation();
591
+ if (
592
+ (exitAfterBuy && !AmountMath.isEmpty(collateralSold)) ||
593
+ AmountMath.isEmpty(alloc.Bid) ||
594
+ ('Collateral' in alloc &&
595
+ AmountMath.isGTE(alloc.Collateral, wanted))
596
+ ) {
597
+ seat.exit();
598
+ helper.removeFromItsBook(key, p);
599
+ } else if (!AmountMath.isGTE(collateralSold, wanted)) {
600
+ helper.updateItsBook(key, collateralSold, p);
601
+ }
602
+ }
603
+ }
604
+
605
+ void facets.helper.publishBookData();
606
+ },
607
+ getCurrentPrice() {
608
+ return this.state.curAuctionPrice;
609
+ },
610
+ hasOrders() {
611
+ const { scaledBidBook, priceBook } = this.state;
612
+ return scaledBidBook.hasOrders() || priceBook.hasOrders();
613
+ },
614
+ captureOraclePriceForRound() {
615
+ const { facets, state } = this;
616
+
617
+ trace(`capturing oracle price `, state.updatingOracleQuote);
618
+ state.capturedPriceForRound = state.updatingOracleQuote;
619
+ void facets.helper.publishBookData();
620
+ },
621
+
622
+ setStartingRate(rate) {
623
+ const { capturedPriceForRound } = this.state;
624
+ capturedPriceForRound !== null ||
625
+ Fail`capturedPriceForRound must be set before each round`;
626
+ assert(capturedPriceForRound);
627
+
628
+ trace('set startPrice', capturedPriceForRound);
629
+ this.state.remainingProceedsGoal = this.state.startProceedsGoal;
630
+ this.state.curAuctionPrice = multiplyRatios(
631
+ capturedPriceForRound,
632
+ rate,
633
+ );
634
+ },
635
+ /**
636
+ * @param {OfferSpec} offerSpec
637
+ * @param {ZCFSeat} seat
638
+ * @param {boolean} trySettle
639
+ */
640
+ addOffer(offerSpec, seat, trySettle) {
641
+ const { bidBrand, collateralBrand } = this.state;
642
+ const offerSpecShape = makeOfferSpecShape(bidBrand, collateralBrand);
643
+
644
+ mustMatch(offerSpec, offerSpecShape);
645
+ const { give } = seat.getProposal();
646
+ const { bidAmountShape } = this.state;
647
+ mustMatch(give.Bid, bidAmountShape, 'give must include "Bid"');
648
+
649
+ const { helper } = this.facets;
650
+ const { exitAfterBuy } = offerSpec;
651
+ if ('offerPrice' in offerSpec) {
652
+ return helper.acceptPriceOffer(
653
+ seat,
654
+ offerSpec.offerPrice,
655
+ offerSpec.maxBuy,
656
+ {
657
+ trySettle,
658
+ exitAfterBuy,
659
+ },
660
+ );
661
+ } else if ('offerBidScaling' in offerSpec) {
662
+ return helper.acceptScaledBidOffer(
663
+ seat,
664
+ offerSpec.offerBidScaling,
665
+ offerSpec.maxBuy,
666
+ {
667
+ trySettle,
668
+ exitAfterBuy,
669
+ },
670
+ );
671
+ } else {
672
+ throw Fail`Offer was neither a price nor a scaledBid`;
673
+ }
674
+ },
675
+ getSeats() {
676
+ const { collateralSeat, bidHoldingSeat } = this.state;
677
+ return { collateralSeat, bidHoldingSeat };
678
+ },
679
+ exitAllSeats() {
680
+ const { priceBook, scaledBidBook } = this.state;
681
+ priceBook.exitAllSeats();
682
+ scaledBidBook.exitAllSeats();
683
+ },
684
+ endAuction() {
685
+ trace('endAuction');
686
+ const { state, facets } = this;
687
+
688
+ state.startCollateral = AmountMath.makeEmpty(state.collateralBrand);
689
+
690
+ state.capturedPriceForRound = null;
691
+ state.curAuctionPrice = null;
692
+ state.remainingProceedsGoal = null;
693
+ state.startProceedsGoal = null;
694
+ void facets.helper.publishBookData();
695
+ },
696
+ getDataUpdates() {
697
+ return this.state.bookDataKit.subscriber;
698
+ },
699
+ getPublicTopics() {
700
+ return {
701
+ bookData: makeRecorderTopic(
702
+ 'Auction schedule',
703
+ this.state.bookDataKit,
704
+ ),
705
+ };
706
+ },
707
+ },
708
+ },
709
+ {
710
+ finish: ({ state, facets }) => {
711
+ const { collateralBrand, bidBrand, priceAuthority } = state;
712
+ assertAllDefined({ collateralBrand, bidBrand, priceAuthority });
713
+ void E.when(
714
+ E(collateralBrand).getDisplayInfo(),
715
+ ({ decimalPlaces = DEFAULT_DECIMALS }) => {
716
+ // TODO(#6946) use this to keep a current price that can be published in state.
717
+ const quoteNotifier = E(priceAuthority).makeQuoteNotifier(
718
+ AmountMath.make(collateralBrand, 10n ** BigInt(decimalPlaces)),
719
+ bidBrand,
720
+ );
721
+ void observeNotifier(quoteNotifier, {
722
+ updateState: quote => {
723
+ trace(
724
+ `BOOK notifier ${priceFrom(quote).numerator.value}/${
725
+ priceFrom(quote).denominator.value
726
+ }`,
727
+ );
728
+ state.updatingOracleQuote = priceFrom(quote);
729
+ },
730
+ fail: reason => {
731
+ throw Error(
732
+ `auction observer of ${collateralBrand} failed: ${reason}`,
733
+ );
734
+ },
735
+ finish: done => {
736
+ throw Error(
737
+ `auction observer for ${collateralBrand} died: ${done}`,
738
+ );
739
+ },
740
+ });
741
+ },
742
+ );
743
+ void facets.helper.publishBookData();
744
+ },
745
+ stateShape: AuctionBookStateShape,
746
+ },
747
+ );
748
+
749
+ /** @type {(...args: Parameters<typeof makeAuctionBookKit>) => ReturnType<typeof makeAuctionBookKit>['self']} */
750
+ const makeAuctionBook = (...args) => makeAuctionBookKit(...args).self;
751
+ return makeAuctionBook;
752
+ };
753
+ harden(prepareAuctionBook);
754
+
755
+ /** @typedef {ReturnType<ReturnType<typeof prepareAuctionBook>>} AuctionBook */