@agoric/inter-protocol 0.13.3 → 0.13.4-dev-800c2bb.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 (140) hide show
  1. package/README.md +26 -20
  2. package/bundles/bundle-auctioneer-js-meta.json +722 -0
  3. package/bundles/bundle-auctioneer.js +1 -0
  4. package/bundles/bundle-econCommitteeCharter-js-meta.json +514 -0
  5. package/bundles/bundle-econCommitteeCharter.js +1 -0
  6. package/bundles/bundle-feeDistributor-js-meta.json +458 -0
  7. package/bundles/bundle-feeDistributor.js +1 -1
  8. package/bundles/bundle-fluxAggregatorKit-js-meta.json +674 -0
  9. package/bundles/bundle-fluxAggregatorKit.js +1 -0
  10. package/bundles/bundle-psm-js-meta.json +666 -0
  11. package/bundles/bundle-psm.js +1 -1
  12. package/bundles/bundle-reserve-js-meta.json +662 -0
  13. package/bundles/bundle-reserve.js +1 -1
  14. package/bundles/bundle-scaledPriceAuthority-js-meta.json +542 -0
  15. package/bundles/bundle-scaledPriceAuthority.js +1 -1
  16. package/bundles/bundle-vaultFactory-js-meta.json +786 -0
  17. package/bundles/bundle-vaultFactory.js +1 -1
  18. package/exported.js +0 -2
  19. package/package.json +34 -31
  20. package/scripts/add-collateral-core.js +14 -4
  21. package/scripts/build-bundles.js +9 -2
  22. package/scripts/deploy-contracts.js +1 -4
  23. package/scripts/init-core.js +20 -38
  24. package/scripts/invite-committee-core.js +1 -1
  25. package/scripts/manual-price-feed.js +2 -2
  26. package/scripts/price-feed-core.js +7 -6
  27. package/scripts/start-local-chain.sh +84 -0
  28. package/src/auction/auctionBook.js +755 -0
  29. package/src/auction/auctioneer.js +720 -0
  30. package/src/auction/offerBook.js +221 -0
  31. package/src/auction/params.js +174 -0
  32. package/src/auction/scheduleMath.js +167 -0
  33. package/src/auction/scheduler.js +370 -0
  34. package/src/auction/sortedOffers.js +136 -0
  35. package/src/auction/util.js +55 -0
  36. package/src/clientSupport.js +324 -0
  37. package/src/collectFees.js +8 -14
  38. package/src/contractSupport.js +24 -68
  39. package/src/econCommitteeCharter.js +161 -77
  40. package/src/feeDistributor.js +60 -20
  41. package/src/index.js +2 -0
  42. package/src/interest-math.js +1 -4
  43. package/src/interest.js +20 -13
  44. package/src/price/README.md +57 -0
  45. package/src/price/fluxAggregatorContract.js +190 -0
  46. package/src/price/fluxAggregatorKit.js +375 -0
  47. package/src/price/priceOracleKit.js +127 -0
  48. package/src/price/roundsManager.js +726 -0
  49. package/src/proposals/addAssetToVault.js +89 -94
  50. package/src/proposals/committee-proposal.js +141 -40
  51. package/src/proposals/core-proposal.js +100 -167
  52. package/src/proposals/econ-behaviors.js +320 -564
  53. package/src/proposals/price-feed-proposal.js +175 -78
  54. package/src/proposals/startEconCommittee.js +25 -12
  55. package/src/proposals/startPSM.js +231 -211
  56. package/src/proposals/utils.js +31 -17
  57. package/src/psm/README.md +71 -0
  58. package/src/psm/psm.js +272 -160
  59. package/src/psm/types.js +2 -0
  60. package/src/reserve/assetReserve.js +56 -413
  61. package/src/reserve/assetReserveKit.js +303 -0
  62. package/src/reserve/doc.md +1 -3
  63. package/src/reserve/params.js +2 -5
  64. package/src/typeGuards.js +13 -0
  65. package/src/vaultFactory/Liquidation.md +100 -0
  66. package/src/vaultFactory/burn.js +2 -1
  67. package/src/vaultFactory/liquidation.js +270 -108
  68. package/src/vaultFactory/math.js +95 -0
  69. package/src/vaultFactory/orderedVaultStore.js +0 -1
  70. package/src/vaultFactory/params.js +133 -112
  71. package/src/vaultFactory/prioritizedVaults.js +36 -79
  72. package/src/vaultFactory/storeUtils.js +67 -9
  73. package/src/vaultFactory/type-imports.js +2 -0
  74. package/src/vaultFactory/types.js +22 -26
  75. package/src/vaultFactory/vault.js +772 -709
  76. package/src/vaultFactory/vaultDirector.js +480 -483
  77. package/src/vaultFactory/vaultFactory.js +99 -60
  78. package/src/vaultFactory/vaultHolder.js +121 -95
  79. package/src/vaultFactory/vaultKit.js +39 -31
  80. package/src/vaultFactory/vaultManager.js +1249 -868
  81. package/bundles/bundle-amm-meta.js +0 -618
  82. package/bundles/bundle-amm.js +0 -1
  83. package/bundles/bundle-feeDistributor-meta.js +0 -346
  84. package/bundles/bundle-interchainPool-meta.js +0 -534
  85. package/bundles/bundle-interchainPool.js +0 -1
  86. package/bundles/bundle-liquidateIncrementally-meta.js +0 -442
  87. package/bundles/bundle-liquidateIncrementally.js +0 -1
  88. package/bundles/bundle-liquidateMinimum-meta.js +0 -442
  89. package/bundles/bundle-liquidateMinimum.js +0 -1
  90. package/bundles/bundle-psm-meta.js +0 -538
  91. package/bundles/bundle-psmCharter-meta.js +0 -418
  92. package/bundles/bundle-psmCharter.js +0 -1
  93. package/bundles/bundle-reserve-meta.js +0 -542
  94. package/bundles/bundle-scaledPriceAuthority-meta.js +0 -442
  95. package/bundles/bundle-stakeFactory-meta.js +0 -574
  96. package/bundles/bundle-stakeFactory.js +0 -1
  97. package/bundles/bundle-vaultFactory-meta.js +0 -598
  98. package/scripts/add-initial-liquidity.js +0 -180
  99. package/src/collect.js +0 -13
  100. package/src/interchainPool.js +0 -135
  101. package/src/makeTracer.js +0 -36
  102. package/src/my-lien.js +0 -85
  103. package/src/proposals/demoIssuers.js +0 -685
  104. package/src/proposals/sim-behaviors.js +0 -40
  105. package/src/psm/psmCharter.js +0 -139
  106. package/src/stakeFactory/README.md +0 -62
  107. package/src/stakeFactory/attestation.js +0 -267
  108. package/src/stakeFactory/attestationTool.js +0 -66
  109. package/src/stakeFactory/constants.js +0 -16
  110. package/src/stakeFactory/params.js +0 -31
  111. package/src/stakeFactory/stakeFactory.js +0 -236
  112. package/src/stakeFactory/stakeFactoryKit.js +0 -455
  113. package/src/stakeFactory/stakeFactoryManager.js +0 -296
  114. package/src/stakeFactory/types.js +0 -42
  115. package/src/vaultFactory/liquidateIncrementally.js +0 -350
  116. package/src/vaultFactory/liquidateMinimum.js +0 -100
  117. package/src/vpool-xyk-amm/AttackersGuide.md +0 -89
  118. package/src/vpool-xyk-amm/addLiquidity.js +0 -222
  119. package/src/vpool-xyk-amm/addPool.js +0 -197
  120. package/src/vpool-xyk-amm/constantProduct/AMM-Trade.jpeg +0 -0
  121. package/src/vpool-xyk-amm/constantProduct/README.md +0 -204
  122. package/src/vpool-xyk-amm/constantProduct/calcFees.js +0 -99
  123. package/src/vpool-xyk-amm/constantProduct/calcSwapPrices.js +0 -85
  124. package/src/vpool-xyk-amm/constantProduct/core.js +0 -153
  125. package/src/vpool-xyk-amm/constantProduct/defaults.js +0 -5
  126. package/src/vpool-xyk-amm/constantProduct/getXY.js +0 -52
  127. package/src/vpool-xyk-amm/constantProduct/internal-types.js +0 -116
  128. package/src/vpool-xyk-amm/constantProduct/invariants.js +0 -43
  129. package/src/vpool-xyk-amm/constantProduct/swap.js +0 -272
  130. package/src/vpool-xyk-amm/constantProduct/types.js +0 -32
  131. package/src/vpool-xyk-amm/doublePool.js +0 -194
  132. package/src/vpool-xyk-amm/estimate.js +0 -155
  133. package/src/vpool-xyk-amm/multipoolMarketMaker.js +0 -520
  134. package/src/vpool-xyk-amm/params.js +0 -61
  135. package/src/vpool-xyk-amm/pool.js +0 -396
  136. package/src/vpool-xyk-amm/priceAuthority.js +0 -94
  137. package/src/vpool-xyk-amm/removeLiquidity.js +0 -31
  138. package/src/vpool-xyk-amm/singlePool.js +0 -73
  139. package/src/vpool-xyk-amm/swap.js +0 -69
  140. 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 */