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