@agoric/inter-protocol 0.16.2-dev-6ad0038.0.6ad0038 → 0.16.2-dev-b95c1c8.0.b95c1c8

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 (52) hide show
  1. package/package.json +16 -16
  2. package/src/index.js +1 -0
  3. package/src/proposals/addAssetToVault.js +3 -97
  4. package/src/proposals/committee-proposal.js +3 -9
  5. package/src/proposals/core-proposal.js +1 -32
  6. package/src/proposals/econ-behaviors.js +1 -140
  7. package/src/vaultFactory/params.d.ts.map +1 -1
  8. package/src/vaultFactory/params.js +1 -0
  9. package/src/vaultFactory/types-ambient.d.ts +0 -2
  10. package/src/vaultFactory/types-ambient.d.ts.map +1 -1
  11. package/src/vaultFactory/types-ambient.js +0 -3
  12. package/src/vaultFactory/vaultDirector.d.ts +5 -40
  13. package/src/vaultFactory/vaultDirector.d.ts.map +1 -1
  14. package/src/vaultFactory/vaultDirector.js +3 -58
  15. package/src/vaultFactory/vaultFactory.d.ts +4 -22
  16. package/src/vaultFactory/vaultFactory.d.ts.map +1 -1
  17. package/src/vaultFactory/vaultFactory.js +2 -9
  18. package/src/vaultFactory/vaultManager.d.ts +2 -180
  19. package/src/vaultFactory/vaultManager.d.ts.map +1 -1
  20. package/src/vaultFactory/vaultManager.js +3 -300
  21. package/src/auction/auctionBook.d.ts +0 -150
  22. package/src/auction/auctionBook.d.ts.map +0 -1
  23. package/src/auction/auctionBook.js +0 -796
  24. package/src/auction/auctionMath.d.ts +0 -18
  25. package/src/auction/auctionMath.d.ts.map +0 -1
  26. package/src/auction/auctionMath.js +0 -82
  27. package/src/auction/auctioneer.d.ts +0 -76
  28. package/src/auction/auctioneer.d.ts.map +0 -1
  29. package/src/auction/auctioneer.js +0 -745
  30. package/src/auction/offerBook.d.ts +0 -47
  31. package/src/auction/offerBook.d.ts.map +0 -1
  32. package/src/auction/offerBook.js +0 -227
  33. package/src/auction/params.d.ts +0 -156
  34. package/src/auction/params.d.ts.map +0 -1
  35. package/src/auction/params.js +0 -184
  36. package/src/auction/scheduleMath.d.ts +0 -8
  37. package/src/auction/scheduleMath.d.ts.map +0 -1
  38. package/src/auction/scheduleMath.js +0 -174
  39. package/src/auction/scheduler.d.ts +0 -57
  40. package/src/auction/scheduler.d.ts.map +0 -1
  41. package/src/auction/scheduler.js +0 -391
  42. package/src/auction/sortedOffers.d.ts +0 -9
  43. package/src/auction/sortedOffers.d.ts.map +0 -1
  44. package/src/auction/sortedOffers.js +0 -141
  45. package/src/proposals/add-auction.js +0 -290
  46. package/src/proposals/upgrade-vaults.js +0 -212
  47. package/src/vaultFactory/liquidation.d.ts +0 -29
  48. package/src/vaultFactory/liquidation.d.ts.map +0 -1
  49. package/src/vaultFactory/liquidation.js +0 -313
  50. package/src/vaultFactory/proceeds.d.ts +0 -36
  51. package/src/vaultFactory/proceeds.d.ts.map +0 -1
  52. package/src/vaultFactory/proceeds.js +0 -285
@@ -1,745 +0,0 @@
1
- /// <reference types="@agoric/governance/exported.js" />
2
- /// <reference types="@agoric/zoe/exported.js" />
3
-
4
- import { Fail, q } from '@endo/errors';
5
- import { E } from '@endo/eventual-send';
6
- import { Far } from '@endo/marshal';
7
- import { AmountMath, AmountShape, BrandShape } from '@agoric/ertp';
8
- import { handleParamGovernance } from '@agoric/governance';
9
- import { makeTracer } from '@agoric/internal';
10
- import { wrapRemoteMarshaller } from '@agoric/internal/src/marshal/wrap-marshaller.js';
11
- import { prepareDurablePublishKit } from '@agoric/notifier';
12
- import { mustMatch } from '@agoric/store';
13
- import { appendToStoredArray } from '@agoric/store/src/stores/store-utils.js';
14
- import { M, provideDurableMapStore } from '@agoric/vat-data';
15
- import {
16
- ceilDivideBy,
17
- ceilMultiplyBy,
18
- defineERecorderKit,
19
- defineRecorderKit,
20
- floorDivideBy,
21
- floorMultiplyBy,
22
- makeRatio,
23
- makeRatioFromAmounts,
24
- makeRecorderTopic,
25
- natSafeMath,
26
- prepareRecorder,
27
- provideEmptySeat,
28
- offerTo,
29
- } from '@agoric/zoe/src/contractSupport/index.js';
30
- import { FullProposalShape } from '@agoric/zoe/src/typeGuards.js';
31
-
32
- import { makeNatAmountShape } from '../contractSupport.js';
33
- import { makeOfferSpecShape, prepareAuctionBook } from './auctionBook.js';
34
- import { auctioneerParamTypes } from './params.js';
35
- import { makeScheduler } from './scheduler.js';
36
- import { AuctionState } from './util.js';
37
-
38
- /**
39
- * @import {Remote, TypedPattern} from '@agoric/internal';
40
- * @import {MapStore} from '@agoric/store';
41
- * @import {Baggage} from '@agoric/vat-data';
42
- * @import {ContractOf} from '@agoric/zoe/src/zoeService/utils.js';
43
- * @import {PriceAuthority, PriceDescription, PriceQuote, PriceQuoteValue, PriceQuery,} from '@agoric/zoe/tools/types.js';
44
- * @import {TimerService} from '@agoric/time';
45
- * @import {AuctionBook} from './auctionBook.js';
46
- * @import {ScheduleNotification} from './scheduler.js';
47
- * @import {OfferSpec} from './auctionBook.js';
48
- * @import {FullSchedule} from './scheduler.js';
49
- * @import {AssetReservePublicFacet} from '../reserve/assetReserve.js';
50
- */
51
-
52
- const BASIS_POINTS = 10_000n;
53
-
54
- const { add, multiply } = natSafeMath;
55
-
56
- const trace = makeTracer('Auction', true);
57
-
58
- /**
59
- * @file In this file, 'Bid' is the name of the ERTP issuer used to purchase
60
- * collateral from various issuers. It's too confusing to also use Bid as a
61
- * verb or a description of amounts offered, so we've tried to find
62
- * alternatives in all those cases.
63
- */
64
-
65
- const MINIMUM_BID_GIVE = 1n;
66
-
67
- /**
68
- * @param {NatValue} rate
69
- * @param {Brand<'nat'>} bidBrand
70
- * @param {Brand<'nat'>} collateralBrand
71
- */
72
- const makeBPRatio = (rate, bidBrand, collateralBrand = bidBrand) =>
73
- makeRatioFromAmounts(
74
- AmountMath.make(bidBrand, rate),
75
- AmountMath.make(collateralBrand, BASIS_POINTS),
76
- );
77
-
78
- /**
79
- * The auction sold some amount of collateral, and raised a certain amount of
80
- * Bid. The excess collateral was returned as `unsoldCollateral`. The Bid amount
81
- * collected from the auction participants is `proceeds`.
82
- *
83
- * Return a set of transfers for atomicRearrange() that distribute
84
- * `unsoldCollateral` and `proceeds` proportionally to each seat's deposited
85
- * amount. Any uneven split should be allocated to the reserve.
86
- *
87
- * @param {Amount} unsoldCollateral
88
- * @param {Amount} proceeds
89
- * @param {{ seat: ZCFSeat; amount: Amount<'nat'>; goal: Amount<'nat'> }[]} deposits
90
- * @param {ZCFSeat} collateralSeat
91
- * @param {ZCFSeat} bidHoldingSeat seat with the Bid allocation to be
92
- * distributed
93
- * @param {string} collateralKeyword The Reserve will hold multiple collaterals,
94
- * so they need distinct keywords
95
- * @param {ZCFSeat} reserveSeat
96
- * @param {Brand} brand
97
- */
98
- const distributeProportionalShares = (
99
- unsoldCollateral,
100
- proceeds,
101
- deposits,
102
- collateralSeat,
103
- bidHoldingSeat,
104
- collateralKeyword,
105
- reserveSeat,
106
- brand,
107
- ) => {
108
- const totalCollDeposited = deposits.reduce((prev, { amount }) => {
109
- return AmountMath.add(prev, amount);
110
- }, AmountMath.makeEmpty(brand));
111
-
112
- const collShare = makeRatioFromAmounts(unsoldCollateral, totalCollDeposited);
113
- const currShare = makeRatioFromAmounts(proceeds, totalCollDeposited);
114
- /** @type {TransferPart[]} */
115
- const transfers = [];
116
- let proceedsLeft = proceeds;
117
- let collateralLeft = unsoldCollateral;
118
-
119
- // each depositor gets a share that equals their amount deposited
120
- // divided by the total deposited multiplied by the Bid and
121
- // collateral being distributed.
122
- for (const { seat, amount } of deposits.values()) {
123
- const currPortion = floorMultiplyBy(amount, currShare);
124
- proceedsLeft = AmountMath.subtract(proceedsLeft, currPortion);
125
- const collPortion = floorMultiplyBy(amount, collShare);
126
- collateralLeft = AmountMath.subtract(collateralLeft, collPortion);
127
- transfers.push([bidHoldingSeat, seat, { Bid: currPortion }]);
128
- transfers.push([collateralSeat, seat, { Collateral: collPortion }]);
129
- }
130
-
131
- transfers.push([bidHoldingSeat, reserveSeat, { Bid: proceedsLeft }]);
132
-
133
- if (!AmountMath.isEmpty(collateralLeft)) {
134
- transfers.push([
135
- collateralSeat,
136
- reserveSeat,
137
- { Collateral: collateralLeft },
138
- { [collateralKeyword]: collateralLeft },
139
- ]);
140
- }
141
-
142
- return transfers;
143
- };
144
-
145
- /**
146
- * The auction sold some amount of collateral, and raised a certain amount of
147
- * Bid. The excess collateral was returned as `unsoldCollateral`. The Bid amount
148
- * collected from the auction participants is `proceeds`.
149
- *
150
- * Return a set of transfers for atomicRearrange() that distribute
151
- * `unsoldCollateral` and `proceeds` proportionally to each seat's deposited
152
- * amount. Any uneven split should be allocated to the reserve.
153
- *
154
- * This function is exported for testability, and is not expected to be used
155
- * outside the contract below.
156
- *
157
- * Some or all of the depositors may have specified a goal amount.
158
- *
159
- * - A if none did, return collateral and Bid prorated to deposits.
160
- * - B if proceeds < proceedsGoal everyone gets prorated amounts of both.
161
- * - C if proceeds matches proceedsGoal, everyone gets the Bid they asked for,
162
- * plus enough collateral to reach the same proportional payout. If any
163
- * depositor's goal amount exceeded their share of the total, we'll fall back
164
- * to the first approach.
165
- * - D if proceeds > proceedsGoal && all depositors specified a limit, all
166
- * depositors get their goal first, then we distribute the remainder
167
- * (collateral and Bid) to get the same proportional payout.
168
- * - E if proceeds > proceedsGoal && some depositors didn't specify a limit,
169
- * depositors who did will get their goal first, then we distribute the
170
- * remainder (collateral and Bid) to get the same proportional payout. If any
171
- * depositor's goal amount exceeded their share of the total, we'll fall back
172
- * as above. Think of it this way: those who specified a limit want as much
173
- * collateral back as possible, consistent with raising a certain amount of
174
- * Bid. Those who didn't specify a limit are trying to sell collateral, and
175
- * would prefer to have as much as possible converted to Bid.
176
- *
177
- * @param {Amount<'nat'>} unsoldCollateral
178
- * @param {Amount<'nat'>} proceeds
179
- * @param {{ seat: ZCFSeat; amount: Amount<'nat'>; goal: Amount<'nat'> }[]} deposits
180
- * @param {ZCFSeat} collateralSeat
181
- * @param {ZCFSeat} bidHoldingSeat seat with the Bid allocation to be
182
- * distributed
183
- * @param {string} collateralKeyword The Reserve will hold multiple collaterals,
184
- * so they need distinct keywords
185
- * @param {ZCFSeat} reserveSeat
186
- * @param {Brand} brand
187
- */
188
- export const distributeProportionalSharesWithLimits = (
189
- unsoldCollateral,
190
- proceeds,
191
- deposits,
192
- collateralSeat,
193
- bidHoldingSeat,
194
- collateralKeyword,
195
- reserveSeat,
196
- brand,
197
- ) => {
198
- trace('distributeProportionally with limits');
199
- // unmatched is the sum of the deposits by those who didn't specify a goal
200
- const [collDeposited, proceedsGoal, unmatchedDeposits] = deposits.reduce(
201
- (prev, { amount, goal }) => {
202
- const nextDeposit = AmountMath.add(prev[0], amount);
203
- const [proceedsSum, unmatchedSum] = goal
204
- ? [AmountMath.add(goal, prev[1]), prev[2]]
205
- : [prev[1], AmountMath.add(prev[2], amount)];
206
- return [nextDeposit, proceedsSum, unmatchedSum];
207
- },
208
- [
209
- AmountMath.makeEmpty(brand),
210
- AmountMath.makeEmptyFromAmount(proceeds),
211
- AmountMath.makeEmpty(brand),
212
- ],
213
- );
214
-
215
- const distributeProportionally = () =>
216
- distributeProportionalShares(
217
- unsoldCollateral,
218
- proceeds,
219
- deposits,
220
- collateralSeat,
221
- bidHoldingSeat,
222
- collateralKeyword,
223
- reserveSeat,
224
- brand,
225
- );
226
-
227
- // cases A and B
228
- if (
229
- AmountMath.isEmpty(proceedsGoal) ||
230
- !AmountMath.isGTE(proceeds, proceedsGoal)
231
- ) {
232
- return distributeProportionally();
233
- }
234
-
235
- // Calculate multiplier for collateral that gives total value each depositor
236
- // should get.
237
- //
238
- // The average price of collateral is proceeds / CollateralSold.
239
- // The value of Collateral is Price * unsoldCollateral.
240
- // The overall total value to be distributed is
241
- // Proceeds + collateralValue.
242
- // Each depositor should get bid and collateral that sum to the overall
243
- // total value multiplied by the ratio of that depositor's collateral
244
- // deposited to all the collateral deposited.
245
- //
246
- // To improve the resolution of the result, we only divide once, so we
247
- // multiply each depositor's collateral remaining by this expression.
248
- //
249
- // collSold * proceeds + proceeds * unsoldCollateral
250
- // -----------------------------------------------------------
251
- // collSold * totalCollDeposit
252
- //
253
- // If you do the dimension analysis, we'll multiply collateral by a ratio
254
- // representing Bid/collateral.
255
-
256
- // average value of collateral is collateralSold / proceeds
257
- const collateralSold = AmountMath.subtract(collDeposited, unsoldCollateral);
258
- const numeratorValue = add(
259
- multiply(collateralSold.value, proceeds.value),
260
- multiply(unsoldCollateral.value, proceeds.value),
261
- );
262
- const denominatorValue = multiply(collateralSold.value, collDeposited.value);
263
- const totalValueRatio = makeRatioFromAmounts(
264
- AmountMath.make(proceeds.brand, numeratorValue),
265
- AmountMath.make(brand, denominatorValue),
266
- );
267
-
268
- const avgPrice = makeRatioFromAmounts(proceeds, collateralSold);
269
-
270
- // Allocate the proceedsGoal amount to depositors who specified it. Add
271
- // collateral to reach their share. Then see what's left, and allocate it
272
- // among the remaining depositors. Escape to distributeProportionalShares if
273
- // anything doesn't work.
274
- /** @type {TransferPart[]} */
275
- const transfers = [];
276
- let proceedsLeft = proceeds;
277
- let collateralLeft = unsoldCollateral;
278
-
279
- // case C
280
- if (AmountMath.isEqual(proceedsGoal, proceeds)) {
281
- // each depositor gets a share that equals their amount deposited
282
- // multiplied by totalValueRatio computed above.
283
-
284
- for (const { seat, amount, goal } of deposits.values()) {
285
- const depositorValue = floorMultiplyBy(amount, totalValueRatio);
286
- if (goal === null || AmountMath.isGTE(depositorValue, goal)) {
287
- let valueNeeded = depositorValue;
288
- if (goal !== null && !AmountMath.isEmpty(goal)) {
289
- proceedsLeft = AmountMath.subtract(proceedsLeft, goal);
290
- transfers.push([bidHoldingSeat, seat, { Bid: goal }]);
291
- valueNeeded = AmountMath.subtract(depositorValue, goal);
292
- }
293
-
294
- const collateralToAdd = floorDivideBy(valueNeeded, avgPrice);
295
- collateralLeft = AmountMath.subtract(collateralLeft, collateralToAdd);
296
- transfers.push([collateralSeat, seat, { Collateral: collateralToAdd }]);
297
- } else {
298
- // This depositor asked for more than their share.
299
- // ignore `transfers` and distribute everything proportionally.
300
- return distributeProportionally();
301
- }
302
- }
303
- } else {
304
- // Cases D & E. Proceeds > proceedsGoal, so those who specified a limit
305
- // receive at least their target.
306
-
307
- const collateralValue = floorMultiplyBy(unsoldCollateral, avgPrice);
308
- const totalDistributableValue = AmountMath.add(proceeds, collateralValue);
309
- // The share for those who specified a limit is proportional to their
310
- // collateral. ceiling because it's a lower limit on the restrictive branch
311
- const limitedShare = ceilMultiplyBy(
312
- AmountMath.subtract(collDeposited, unmatchedDeposits),
313
- makeRatioFromAmounts(totalDistributableValue, collDeposited),
314
- );
315
-
316
- // if proceedsGoal + value of unsoldCollateral >= limitedShare then those
317
- // who specified a limit can get all the excess over their limit in
318
- // collateral. Others share whatever is left.
319
- // If proceedsGoal + unsoldCollateral < limitedShare then those who
320
- // specified share all the collateral, and everyone gets Bid to cover
321
- // the remainder of their share.
322
- const limitedGetMaxCollateral = AmountMath.isGTE(
323
- AmountMath.add(proceedsGoal, collateralValue),
324
- limitedShare,
325
- );
326
-
327
- const calcNotLimitedCollateralShare = () => {
328
- if (limitedGetMaxCollateral) {
329
- // those who limited will get limitedShare - proceedsGoal in collateral
330
- const ltdCollatValue = AmountMath.subtract(limitedShare, proceedsGoal);
331
- const ltdCollatShare = ceilDivideBy(ltdCollatValue, avgPrice);
332
- // the unlimited will get the remainder of the collateral
333
- return AmountMath.subtract(unsoldCollateral, ltdCollatShare);
334
- } else {
335
- return AmountMath.makeEmpty(brand);
336
- }
337
- };
338
- const notLimitedCollateralShare = calcNotLimitedCollateralShare();
339
-
340
- for (const { seat, amount, goal } of deposits.values()) {
341
- const depositorValue = floorMultiplyBy(amount, totalValueRatio);
342
-
343
- const addRemainderInBid = collateralAdded => {
344
- const collateralVal = ceilMultiplyBy(collateralAdded, avgPrice);
345
- /** @type {Amount<'nat'>} XXX for package depth type resolution */
346
- const valueNeeded = AmountMath.subtract(depositorValue, collateralVal);
347
-
348
- proceedsLeft = AmountMath.subtract(proceedsLeft, valueNeeded);
349
- transfers.push([bidHoldingSeat, seat, { Bid: valueNeeded }]);
350
- };
351
-
352
- if (goal === null || AmountMath.isEmpty(goal)) {
353
- const collateralShare = floorMultiplyBy(
354
- notLimitedCollateralShare,
355
- makeRatioFromAmounts(amount, unmatchedDeposits),
356
- );
357
- collateralLeft = AmountMath.subtract(collateralLeft, collateralShare);
358
- addRemainderInBid(collateralShare);
359
- transfers.push([collateralSeat, seat, { Collateral: collateralShare }]);
360
- } else if (limitedGetMaxCollateral) {
361
- proceedsLeft = AmountMath.subtract(proceedsLeft, goal);
362
- transfers.push([bidHoldingSeat, seat, { Bid: goal }]);
363
-
364
- const valueNeeded = AmountMath.subtract(depositorValue, goal);
365
- const collateralToAdd = floorDivideBy(valueNeeded, avgPrice);
366
- collateralLeft = AmountMath.subtract(collateralLeft, collateralToAdd);
367
- transfers.push([collateralSeat, seat, { Collateral: collateralToAdd }]);
368
- } else {
369
- // There's not enough collateral to completely cover the gap above
370
- // the proceedsGoal amount, so each depositor gets a proportional share
371
- // of unsoldCollateral plus enough Bid to reach their share.
372
- const collateralShare = floorMultiplyBy(
373
- unsoldCollateral,
374
- makeRatioFromAmounts(amount, collDeposited),
375
- );
376
- collateralLeft = AmountMath.subtract(collateralLeft, collateralShare);
377
- addRemainderInBid(collateralShare);
378
- transfers.push([collateralSeat, seat, { Collateral: collateralShare }]);
379
- }
380
- }
381
- }
382
-
383
- transfers.push([bidHoldingSeat, reserveSeat, { Bid: proceedsLeft }]);
384
-
385
- if (!AmountMath.isEmpty(collateralLeft)) {
386
- transfers.push([
387
- collateralSeat,
388
- reserveSeat,
389
- { Collateral: collateralLeft },
390
- { [collateralKeyword]: collateralLeft },
391
- ]);
392
- }
393
- return transfers;
394
- };
395
-
396
- /**
397
- * @param {ZCF<
398
- * GovernanceTerms<typeof auctioneerParamTypes> & {
399
- * timerService: TimerService;
400
- * reservePublicFacet: AssetReservePublicFacet;
401
- * priceAuthority: PriceAuthority;
402
- * }
403
- * >} zcf
404
- * @param {{
405
- * initialPoserInvitation: Invitation;
406
- * storageNode: Remote<StorageNode>;
407
- * marshaller: Remote<Marshaller>;
408
- * }} privateArgs
409
- * @param {Baggage} baggage
410
- */
411
- export const start = async (zcf, privateArgs, baggage) => {
412
- const { brands, timerService: timer, priceAuthority } = zcf.getTerms();
413
- timer || Fail`Timer must be in Auctioneer terms`;
414
- const timerBrand = await E(timer).getTimerBrand();
415
-
416
- const bidAmountShape = { brand: brands.Bid, value: M.nat() };
417
-
418
- /** @type {MapStore<Brand, AuctionBook>} */
419
- const books = provideDurableMapStore(baggage, 'auctionBooks');
420
- /**
421
- * @type {MapStore<
422
- * Brand,
423
- * { seat: ZCFSeat; amount: Amount<'nat'>; goal: Amount<'nat'> }[]
424
- * >}
425
- */
426
- const deposits = provideDurableMapStore(baggage, 'deposits');
427
- /** @type {MapStore<Brand, Keyword>} */
428
- const brandToKeyword = provideDurableMapStore(baggage, 'brandToKeyword');
429
-
430
- const reserveSeat = provideEmptySeat(zcf, baggage, 'collateral');
431
-
432
- let bookCounter = 0;
433
-
434
- const { marshaller: remoteMarshaller } = privateArgs;
435
-
436
- const cachingMarshaller = wrapRemoteMarshaller(remoteMarshaller);
437
-
438
- const makeDurablePublishKit = prepareDurablePublishKit(
439
- baggage,
440
- 'Auction publish kit',
441
- );
442
- const makeRecorder = prepareRecorder(baggage, cachingMarshaller);
443
-
444
- const makeRecorderKit = defineRecorderKit({
445
- makeRecorder,
446
- makeDurablePublishKit,
447
- });
448
-
449
- const makeAuctionBook = prepareAuctionBook(baggage, zcf, makeRecorderKit);
450
-
451
- const makeERecorderKit = defineERecorderKit({
452
- makeRecorder,
453
- makeDurablePublishKit,
454
- });
455
- const scheduleKit = makeERecorderKit(
456
- E(privateArgs.storageNode).makeChildNode('schedule'),
457
- /**
458
- * @type {TypedPattern<ScheduleNotification>}
459
- */ (M.any()),
460
- );
461
-
462
- /**
463
- * @param {ZCFSeat} seat
464
- * @param {Amount<'nat'>} amount
465
- * @param {Amount<'nat'> | null} goal
466
- */
467
- const addDeposit = (seat, amount, goal = null) => {
468
- appendToStoredArray(deposits, amount.brand, harden({ seat, amount, goal }));
469
- };
470
-
471
- const sendToReserve = keyword => {
472
- const { reservePublicFacet } = zcf.getTerms();
473
-
474
- const amount = reserveSeat.getCurrentAllocation()[keyword];
475
- if (!amount || AmountMath.isEmpty(amount)) {
476
- return;
477
- }
478
-
479
- const invitation = E(reservePublicFacet).makeAddCollateralInvitation();
480
- // don't wait for a response
481
- void E.when(invitation, invite => {
482
- const proposal = { give: { Collateral: amount } };
483
- void offerTo(
484
- zcf,
485
- invite,
486
- { [keyword]: 'Collateral' },
487
- proposal,
488
- reserveSeat,
489
- );
490
- });
491
- };
492
-
493
- // Called "discount" rate even though it can be above or below 100%.
494
- /** @type {NatValue} */
495
- let currentDiscountRateBP;
496
-
497
- const distributeProceeds = () => {
498
- for (const brand of deposits.keys()) {
499
- const book = books.get(brand);
500
- const { collateralSeat, bidHoldingSeat } = book.getSeats();
501
-
502
- const depositsForBrand = deposits.get(brand);
503
- if (depositsForBrand.length === 1) {
504
- // send it all to the one
505
- const liqSeat = depositsForBrand[0].seat;
506
-
507
- zcf.atomicRearrange(
508
- harden([
509
- [collateralSeat, liqSeat, collateralSeat.getCurrentAllocation()],
510
- [bidHoldingSeat, liqSeat, bidHoldingSeat.getCurrentAllocation()],
511
- ]),
512
- );
513
- liqSeat.exit();
514
- deposits.set(brand, []);
515
- } else if (depositsForBrand.length > 1) {
516
- const collProceeds = collateralSeat.getCurrentAllocation().Collateral;
517
- const currProceeds =
518
- bidHoldingSeat.getCurrentAllocation().Bid ||
519
- AmountMath.makeEmpty(brands.Bid);
520
- const transfers = distributeProportionalSharesWithLimits(
521
- collProceeds,
522
- currProceeds,
523
- depositsForBrand,
524
- collateralSeat,
525
- bidHoldingSeat,
526
- brandToKeyword.get(brand),
527
- reserveSeat,
528
- brand,
529
- );
530
- zcf.atomicRearrange(harden(transfers));
531
-
532
- for (const { seat } of depositsForBrand) {
533
- seat.exit();
534
- }
535
-
536
- sendToReserve(brandToKeyword.get(brand));
537
- deposits.set(brand, []);
538
- }
539
- }
540
- };
541
-
542
- const { augmentPublicFacet, makeFarGovernorFacet, params } =
543
- await handleParamGovernance(
544
- zcf,
545
- privateArgs.initialPoserInvitation,
546
- auctioneerParamTypes,
547
- privateArgs.storageNode,
548
- cachingMarshaller,
549
- );
550
-
551
- const tradeEveryBook = () => {
552
- const offerScalingRatio = makeRatio(
553
- currentDiscountRateBP,
554
- brands.Bid,
555
- BASIS_POINTS,
556
- );
557
-
558
- for (const book of books.values()) {
559
- book.settleAtNewRate(offerScalingRatio);
560
- }
561
- };
562
-
563
- const driver = Far('Auctioneer', {
564
- reducePriceAndTrade: () => {
565
- trace('reducePriceAndTrade');
566
-
567
- natSafeMath.isGTE(currentDiscountRateBP, params.getDiscountStep()) ||
568
- Fail`rates must fall ${currentDiscountRateBP}`;
569
-
570
- currentDiscountRateBP = natSafeMath.subtract(
571
- currentDiscountRateBP,
572
- params.getDiscountStep(),
573
- );
574
-
575
- tradeEveryBook();
576
- },
577
- finalize: () => {
578
- trace('finalize');
579
-
580
- for (const book of books.values()) {
581
- book.endAuction();
582
- }
583
- distributeProceeds();
584
- },
585
- startRound() {
586
- trace('startRound');
587
-
588
- currentDiscountRateBP = params.getStartingRate();
589
- for (const book of books.values()) {
590
- book.setStartingRate(makeBPRatio(currentDiscountRateBP, brands.Bid));
591
- }
592
-
593
- tradeEveryBook();
594
- },
595
- capturePrices() {
596
- for (const book of books.values()) {
597
- book.captureOraclePriceForRound();
598
- }
599
- },
600
- });
601
-
602
- const isActive = () => scheduler.getAuctionState() === AuctionState.ACTIVE;
603
-
604
- /**
605
- * @param {ZCFSeat} zcfSeat
606
- * @param {{ goal: Amount<'nat'> }} offerArgs
607
- */
608
- const depositOfferHandler = (zcfSeat, offerArgs) => {
609
- const goalMatcher = M.or(undefined, { goal: bidAmountShape });
610
- mustMatch(offerArgs, harden(goalMatcher));
611
- const { Collateral: collateralAmount } = zcfSeat.getCurrentAllocation();
612
- const book = books.get(collateralAmount.brand);
613
- trace(`deposited ${q(collateralAmount)} goal: ${q(offerArgs?.goal)}`);
614
-
615
- book.addAssets(collateralAmount, zcfSeat, offerArgs?.goal);
616
- addDeposit(zcfSeat, collateralAmount, offerArgs?.goal);
617
- return 'deposited';
618
- };
619
-
620
- const makeDepositInvitation = () =>
621
- zcf.makeInvitation(
622
- depositOfferHandler,
623
- 'deposit Collateral',
624
- undefined,
625
- M.splitRecord({ give: { Collateral: AmountShape } }),
626
- );
627
-
628
- const biddingProposalShape = M.splitRecord(
629
- {
630
- give: {
631
- Bid: makeNatAmountShape(brands.Bid, MINIMUM_BID_GIVE),
632
- },
633
- },
634
- {
635
- maxBuy: M.or({ Collateral: AmountShape }, {}),
636
- exit: FullProposalShape.exit,
637
- },
638
- );
639
-
640
- const publicFacet = augmentPublicFacet(
641
- harden({
642
- /** @param {Brand<'nat'>} collateralBrand */
643
- makeBidInvitation(collateralBrand) {
644
- mustMatch(collateralBrand, BrandShape);
645
- books.has(collateralBrand) ||
646
- Fail`No book for brand ${collateralBrand}`;
647
- const offerSpecShape = makeOfferSpecShape(brands.Bid, collateralBrand);
648
- /**
649
- * @param {ZCFSeat} zcfSeat
650
- * @param {OfferSpec} offerSpec
651
- */
652
- const newBidHandler = (zcfSeat, offerSpec) => {
653
- // xxx consider having Zoe guard the offerArgs with a provided shape
654
- mustMatch(offerSpec, offerSpecShape);
655
- const auctionBook = books.get(collateralBrand);
656
- auctionBook.addOffer(offerSpec, zcfSeat, isActive());
657
- return 'Your bid has been accepted';
658
- };
659
-
660
- return zcf.makeInvitation(
661
- newBidHandler,
662
- 'new bidding offer',
663
- {},
664
- biddingProposalShape,
665
- );
666
- },
667
- getSchedules() {
668
- return scheduler.getSchedule();
669
- },
670
- getScheduleUpdates() {
671
- return scheduleKit.subscriber;
672
- },
673
- getBookDataUpdates(brand) {
674
- return books.get(brand).getDataUpdates();
675
- },
676
- getPublicTopics(brand) {
677
- if (brand) {
678
- return books.get(brand).getPublicTopics();
679
- }
680
-
681
- return {
682
- schedule: makeRecorderTopic('Auction schedule', scheduleKit),
683
- };
684
- },
685
- makeDepositInvitation,
686
- ...params,
687
- }),
688
- );
689
-
690
- const scheduler = await E.when(scheduleKit.recorderP, scheduleRecorder =>
691
- makeScheduler(
692
- driver,
693
- timer,
694
- // @ts-expect-error types are correct. How to convince TS?
695
- params,
696
- timerBrand,
697
- scheduleRecorder,
698
- publicFacet.getSubscription(),
699
- ),
700
- );
701
-
702
- const creatorFacet = makeFarGovernorFacet(
703
- Far('Auctioneer creatorFacet', {
704
- /**
705
- * @param {Issuer<'nat'>} issuer
706
- * @param {Keyword} kwd
707
- */
708
- async addBrand(issuer, kwd) {
709
- zcf.assertUniqueKeyword(kwd);
710
- !baggage.has(kwd) ||
711
- Fail`cannot add brand with keyword ${kwd}. it's in use`;
712
- const { brand } = await zcf.saveIssuer(issuer, kwd);
713
-
714
- const bookId = `book${bookCounter}`;
715
- bookCounter += 1;
716
- /** @type {Remote<StorageNode>} */
717
- const bNode = await E(privateArgs.storageNode).makeChildNode(bookId);
718
-
719
- const newBook = await makeAuctionBook(
720
- brands.Bid,
721
- brand,
722
- priceAuthority,
723
- bNode,
724
- );
725
-
726
- // These three store.init() calls succeed or fail atomically
727
- deposits.init(brand, harden([]));
728
- books.init(brand, newBook);
729
- brandToKeyword.init(brand, kwd);
730
- },
731
- /** @returns {Promise<FullSchedule>} */
732
- getSchedule() {
733
- return E(scheduler).getSchedule();
734
- },
735
- }),
736
- );
737
-
738
- return { publicFacet, creatorFacet };
739
- };
740
-
741
- /** @typedef {ContractOf<typeof start>} AuctioneerContract */
742
- /** @typedef {AuctioneerContract['publicFacet']} AuctioneerPublicFacet */
743
- /** @typedef {AuctioneerContract['creatorFacet']} AuctioneerCreatorFacet */
744
-
745
- export const AuctionPFShape = M.remotable('Auction Public Facet');