@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.
- package/package.json +16 -16
- package/src/index.js +1 -0
- package/src/proposals/addAssetToVault.js +3 -97
- package/src/proposals/committee-proposal.js +3 -9
- package/src/proposals/core-proposal.js +1 -32
- package/src/proposals/econ-behaviors.js +1 -140
- package/src/vaultFactory/params.d.ts.map +1 -1
- package/src/vaultFactory/params.js +1 -0
- package/src/vaultFactory/types-ambient.d.ts +0 -2
- package/src/vaultFactory/types-ambient.d.ts.map +1 -1
- package/src/vaultFactory/types-ambient.js +0 -3
- package/src/vaultFactory/vaultDirector.d.ts +5 -40
- package/src/vaultFactory/vaultDirector.d.ts.map +1 -1
- package/src/vaultFactory/vaultDirector.js +3 -58
- package/src/vaultFactory/vaultFactory.d.ts +4 -22
- package/src/vaultFactory/vaultFactory.d.ts.map +1 -1
- package/src/vaultFactory/vaultFactory.js +2 -9
- package/src/vaultFactory/vaultManager.d.ts +2 -180
- package/src/vaultFactory/vaultManager.d.ts.map +1 -1
- package/src/vaultFactory/vaultManager.js +3 -300
- package/src/auction/auctionBook.d.ts +0 -150
- package/src/auction/auctionBook.d.ts.map +0 -1
- package/src/auction/auctionBook.js +0 -796
- package/src/auction/auctionMath.d.ts +0 -18
- package/src/auction/auctionMath.d.ts.map +0 -1
- package/src/auction/auctionMath.js +0 -82
- package/src/auction/auctioneer.d.ts +0 -76
- package/src/auction/auctioneer.d.ts.map +0 -1
- package/src/auction/auctioneer.js +0 -745
- package/src/auction/offerBook.d.ts +0 -47
- package/src/auction/offerBook.d.ts.map +0 -1
- package/src/auction/offerBook.js +0 -227
- package/src/auction/params.d.ts +0 -156
- package/src/auction/params.d.ts.map +0 -1
- package/src/auction/params.js +0 -184
- package/src/auction/scheduleMath.d.ts +0 -8
- package/src/auction/scheduleMath.d.ts.map +0 -1
- package/src/auction/scheduleMath.js +0 -174
- package/src/auction/scheduler.d.ts +0 -57
- package/src/auction/scheduler.d.ts.map +0 -1
- package/src/auction/scheduler.js +0 -391
- package/src/auction/sortedOffers.d.ts +0 -9
- package/src/auction/sortedOffers.d.ts.map +0 -1
- package/src/auction/sortedOffers.js +0 -141
- package/src/proposals/add-auction.js +0 -290
- package/src/proposals/upgrade-vaults.js +0 -212
- package/src/vaultFactory/liquidation.d.ts +0 -29
- package/src/vaultFactory/liquidation.d.ts.map +0 -1
- package/src/vaultFactory/liquidation.js +0 -313
- package/src/vaultFactory/proceeds.d.ts +0 -36
- package/src/vaultFactory/proceeds.d.ts.map +0 -1
- 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');
|