@agoric/inter-protocol 0.13.4-pismo-dev-0d5327f.0 → 0.14.0

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