@agoric/inter-protocol 0.13.4-pismo-dev-0d5327f.0 → 0.14.1-dev-bf4b59f.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,726 @@
1
+ import { Fail, q } from '@agoric/assert';
2
+ import { AmountMath } from '@agoric/ertp';
3
+ import { TimeMath } from '@agoric/time';
4
+ import { M, makeScalarBigMapStore, prepareExoClassKit } from '@agoric/vat-data';
5
+ import {
6
+ calculateMedian,
7
+ natSafeMath,
8
+ } from '@agoric/zoe/src/contractSupport/index.js';
9
+ import { E } from '@endo/eventual-send';
10
+ import { Far } from '@endo/marshal';
11
+ import { isNat, Nat } from '@endo/nat';
12
+ import { UnguardedHelperI } from '../typeGuards.js';
13
+
14
+ const { add, subtract, multiply, floorDivide, ceilDivide, isGTE } = natSafeMath;
15
+
16
+ /** @typedef {import('./priceOracleKit.js').OracleStatus} OracleStatus */
17
+ /**
18
+ * @typedef {import('@agoric/time/src/types').Timestamp} Timestamp
19
+ * @typedef {import('@agoric/time/src/types').TimerService} TimerService
20
+ */
21
+
22
+ /** @type {string} */
23
+ const V3_NO_DATA_ERROR = 'No data present';
24
+
25
+ /** @type {bigint} */
26
+ export const ROUND_MAX = BigInt(2 ** 32 - 1);
27
+
28
+ /**
29
+ * @param {bigint} roundId
30
+ */
31
+ const validRoundId = roundId => {
32
+ return roundId <= ROUND_MAX;
33
+ };
34
+
35
+ /**
36
+ * @typedef {{ roundId: number | undefined, unitPrice: NatValue }} PriceRound
37
+ *
38
+ * @typedef {Pick<RoundData, 'roundId' | 'startedAt'> & { startedBy: string }} LatestRound
39
+ */
40
+
41
+ /**
42
+ * @typedef {Round & {roundId: bigint}} RoundData
43
+ */
44
+
45
+ /**
46
+ * @typedef {object} Round
47
+ * @property {bigint} answer the answer for the given round
48
+ * @property {Timestamp} startedAt the timestamp when the round was started. This is 0
49
+ * if the round hasn't been started yet.
50
+ * @property {Timestamp} updatedAt the timestamp when the round last was updated (i.e.
51
+ * answer was last computed)
52
+ * @property {bigint} answeredInRound the round ID of the round in which the answer
53
+ * was computed. answeredInRound may be smaller than roundId when the round
54
+ * timed out. answeredInRound is equal to roundId when the round didn't time out
55
+ * and was completed regularly.
56
+ */
57
+
58
+ /**
59
+ * @typedef {object} RoundDetails
60
+ * @property {bigint[]} submissions
61
+ * @property {number} maxSubmissions
62
+ * @property {number} minSubmissions
63
+ * @property {number} roundTimeout
64
+ */
65
+
66
+ /**
67
+ * @typedef {IssuerKit<'set'>} QuoteKit
68
+ */
69
+
70
+ /**
71
+ * @typedef {Readonly<import('./fluxAggregatorKit.js').ChainlinkConfig & {
72
+ * quoteKit: QuoteKit,
73
+ * answerPublisher: Publisher<void>,
74
+ * brandIn: Brand<'nat'>,
75
+ * brandOut: Brand<'nat'>,
76
+ * latestRoundPublisher: import('@agoric/zoe/src/contractSupport/recorder.js').Recorder<LatestRound>,
77
+ * timerPresence: TimerService,
78
+ * }>} HeldParams
79
+ *
80
+ * @typedef {Readonly<HeldParams & {
81
+ * details: MapStore<bigint, RoundDetails>,
82
+ * rounds: MapStore<bigint, Round>,
83
+ * unitIn: bigint,
84
+ * }>} ImmutableState
85
+ *
86
+ * @typedef {{
87
+ * lastValueOutForUnitIn: bigint?,
88
+ * reportingRoundId: bigint,
89
+ * }} MutableState
90
+ */
91
+ /** @typedef {ImmutableState & MutableState} State */
92
+
93
+ export const prepareRoundsManagerKit = baggage =>
94
+ prepareExoClassKit(
95
+ baggage,
96
+ 'RoundsManager',
97
+ {
98
+ helper: UnguardedHelperI,
99
+ contract: M.interface(
100
+ 'contract',
101
+ {
102
+ authenticateQuote: M.call(M.any()).returns(M.any()),
103
+ makeCreateQuote: M.call().optional(M.any()).returns(M.any()),
104
+ eligibleForSpecificRound: M.call(M.any()).returns(M.boolean()),
105
+ getRoundData: M.call(M.any()).returns(M.promise()),
106
+ getRoundStatus: M.call(M.any()).returns(M.record()),
107
+ oracleRoundStateSuggestRound: M.call(M.any()).returns(M.record()),
108
+ },
109
+ // TODO(6571) stop sloppy
110
+ { sloppy: true },
111
+ ),
112
+ oracle: M.interface('oracle', {
113
+ handlePush: M.call(M.record(), M.record()).returns(M.promise()),
114
+ }),
115
+ },
116
+ /** @type {(opts: HeldParams & { unitAmountIn: Amount<'nat'> }) => State} */
117
+ ({
118
+ // ChainlinkConfig
119
+ maxSubmissionCount,
120
+ minSubmissionCount,
121
+ restartDelay,
122
+ minSubmissionValue,
123
+ maxSubmissionValue,
124
+ timeout,
125
+ // other HeldParams
126
+ quoteKit,
127
+ answerPublisher,
128
+ brandIn,
129
+ brandOut,
130
+ latestRoundPublisher,
131
+ timerPresence,
132
+ // other option
133
+ unitAmountIn,
134
+ }) => {
135
+ const unitIn = AmountMath.getValue(brandIn, unitAmountIn);
136
+
137
+ const rounds = makeScalarBigMapStore('rounds', { durable: true });
138
+
139
+ const details = makeScalarBigMapStore('details', { durable: true });
140
+
141
+ /** @type {ImmutableState} */
142
+ const immutable = {
143
+ // ChainlinkConfig
144
+ maxSubmissionCount,
145
+ minSubmissionCount,
146
+ restartDelay,
147
+ minSubmissionValue,
148
+ maxSubmissionValue,
149
+ timeout,
150
+ // other HeldParams
151
+ quoteKit,
152
+ answerPublisher,
153
+ brandIn,
154
+ brandOut,
155
+ latestRoundPublisher,
156
+ timerPresence,
157
+ // computed
158
+ details,
159
+ rounds,
160
+ unitIn,
161
+ };
162
+ return {
163
+ ...immutable,
164
+ lastValueOutForUnitIn: null,
165
+ reportingRoundId: 0n,
166
+ };
167
+ },
168
+ {
169
+ helper: {
170
+ /**
171
+ * @param {bigint} roundId
172
+ */
173
+ acceptingSubmissions(roundId) {
174
+ const { details } = this.state;
175
+ return (
176
+ details.has(roundId) && details.get(roundId).maxSubmissions !== 0
177
+ );
178
+ },
179
+
180
+ /**
181
+ * @param {OracleStatus} status
182
+ * @param {bigint} roundId
183
+ */
184
+ delayed(status, roundId) {
185
+ const { restartDelay } = this.state;
186
+ const lastStarted = status.lastStartedRound;
187
+ return roundId > add(lastStarted, restartDelay) || lastStarted === 0n;
188
+ },
189
+
190
+ /**
191
+ * @param {bigint} roundId
192
+ */
193
+ deleteRoundDetails(roundId) {
194
+ const { details } = this.state;
195
+ const roundDetails = details.get(roundId);
196
+ if (roundDetails.submissions.length < roundDetails.maxSubmissions)
197
+ return;
198
+ details.delete(roundId);
199
+ },
200
+
201
+ /**
202
+ * @param {bigint} roundId
203
+ */
204
+ isNextRound(roundId) {
205
+ const { reportingRoundId } = this.state;
206
+ return roundId === add(reportingRoundId, 1);
207
+ },
208
+
209
+ /**
210
+ * @param {bigint} roundId
211
+ * @param {Timestamp} blockTimestamp
212
+ * @param {string} oracleId
213
+ */
214
+ initializeNewRound(roundId, blockTimestamp, oracleId) {
215
+ const {
216
+ details,
217
+ latestRoundPublisher,
218
+ minSubmissionCount,
219
+ maxSubmissionCount,
220
+ rounds,
221
+ timeout,
222
+ } = this.state;
223
+ const { helper } = this.facets;
224
+ helper.isNextRound(roundId) || Fail`Round ${roundId} already started`;
225
+
226
+ helper.updateTimedOutRoundInfo(subtract(roundId, 1), blockTimestamp);
227
+
228
+ this.state.reportingRoundId = roundId;
229
+
230
+ details.init(
231
+ roundId,
232
+ harden({
233
+ submissions: [],
234
+ maxSubmissions: maxSubmissionCount,
235
+ minSubmissions: minSubmissionCount,
236
+ roundTimeout: timeout,
237
+ }),
238
+ );
239
+
240
+ const round = harden({
241
+ answer: 0n,
242
+ startedAt: blockTimestamp,
243
+ updatedAt: 0n,
244
+ answeredInRound: 0n,
245
+ });
246
+ rounds.init(roundId, round);
247
+ // assume it succeeds. if not, expect the next to.
248
+ // if it fails continuously it'll be apparent in the unhandled rejection logging.
249
+ void latestRoundPublisher.write({
250
+ roundId,
251
+ startedAt: round.startedAt,
252
+ startedBy: oracleId,
253
+ });
254
+ },
255
+
256
+ /**
257
+ * @param {bigint} roundId
258
+ * @param {bigint} rrId reporting round ID
259
+ */
260
+ previousAndCurrentUnanswered(roundId, rrId) {
261
+ const { rounds } = this.state;
262
+ return add(roundId, 1) === rrId && rounds.get(rrId).updatedAt === 0n;
263
+ },
264
+
265
+ /**
266
+ * @param {bigint} roundId
267
+ * @param {OracleStatus} status
268
+ * @param {Timestamp} blockTimestamp
269
+ * @returns {OracleStatus | undefined} the new status
270
+ */
271
+ proposeNewRound(roundId, status, blockTimestamp) {
272
+ const { helper } = this.facets;
273
+ if (!helper.isNextRound(roundId)) return undefined;
274
+ const { restartDelay } = this.state;
275
+ const lastStarted = status.lastStartedRound; // cache storage reads
276
+ if (roundId <= add(lastStarted, restartDelay) && lastStarted !== 0n)
277
+ return undefined;
278
+ helper.initializeNewRound(roundId, blockTimestamp, status.oracleId);
279
+
280
+ return harden({
281
+ ...status,
282
+ lastStartedRound: roundId,
283
+ });
284
+ },
285
+
286
+ /**
287
+ * @param {bigint} submission
288
+ * @param {bigint} roundId
289
+ * @param {OracleStatus} status
290
+ * @returns {OracleStatus} the new status
291
+ */
292
+ recordSubmission(submission, roundId, status) {
293
+ const { helper } = this.facets;
294
+ const { details } = this.state;
295
+ helper.acceptingSubmissions(roundId) ||
296
+ Fail`round ${q(
297
+ Number(roundId),
298
+ )} not accepting submissions from oracle ${q(status.oracleId)}`;
299
+
300
+ const lastRoundDetails = details.get(roundId);
301
+ details.set(roundId, {
302
+ ...lastRoundDetails,
303
+ submissions: [...lastRoundDetails.submissions, submission],
304
+ });
305
+
306
+ return {
307
+ ...status,
308
+ lastReportedRound: roundId,
309
+ latestSubmission: submission,
310
+ };
311
+ },
312
+
313
+ /**
314
+ * @param {bigint} roundId
315
+ * @param {Timestamp} blockTimestamp
316
+ */
317
+ supersedable(roundId, blockTimestamp) {
318
+ const { rounds } = this.state;
319
+ const { helper } = this.facets;
320
+ return (
321
+ rounds.has(roundId) &&
322
+ (TimeMath.absValue(rounds.get(roundId).updatedAt) > 0n ||
323
+ helper.timedOut(roundId, blockTimestamp))
324
+ );
325
+ },
326
+ /**
327
+ * @param {bigint} roundId
328
+ * @param {Timestamp} blockTimestamp
329
+ */
330
+ timedOut(roundId, blockTimestamp) {
331
+ const { details, rounds } = this.state;
332
+ if (!details.has(roundId) || !rounds.has(roundId)) {
333
+ return false;
334
+ }
335
+
336
+ const startedAt = rounds.get(roundId).startedAt;
337
+ const roundTimeout = details.get(roundId).roundTimeout;
338
+ // TODO Better would be to make `roundTimeout` a `RelativeTime`
339
+ // everywhere, and to rename it to a name that does not
340
+ // mistakenly imply that it is an absolute time.
341
+ const roundTimeoutDuration = TimeMath.relValue(BigInt(roundTimeout));
342
+ const roundTimedOut =
343
+ TimeMath.absValue(startedAt) > 0n &&
344
+ TimeMath.relValue(roundTimeoutDuration) > 0n &&
345
+ TimeMath.compareAbs(
346
+ TimeMath.addAbsRel(startedAt, roundTimeoutDuration),
347
+ blockTimestamp,
348
+ ) < 0;
349
+
350
+ return roundTimedOut;
351
+ },
352
+
353
+ /**
354
+ * @param {bigint} roundId
355
+ * @param {Timestamp} blockTimestamp
356
+ */
357
+ updateRoundAnswer(roundId, blockTimestamp) {
358
+ const { answerPublisher, details, rounds } = this.state;
359
+ const roundDetails = details.get(roundId);
360
+ if (roundDetails.submissions.length < roundDetails.minSubmissions) {
361
+ return [false, 0];
362
+ }
363
+
364
+ /** @type {bigint | undefined} */
365
+ // @ts-expect-error faulty inference
366
+ const newAnswer = calculateMedian(
367
+ roundDetails.submissions.filter(
368
+ sample => isNat(sample) && sample > 0n,
369
+ ),
370
+ { add, divide: floorDivide, isGTE },
371
+ );
372
+
373
+ assert(newAnswer, 'insufficient samples');
374
+
375
+ rounds.set(roundId, {
376
+ ...rounds.get(roundId),
377
+ answer: newAnswer,
378
+ updatedAt: blockTimestamp,
379
+ answeredInRound: roundId,
380
+ });
381
+
382
+ this.state.lastValueOutForUnitIn = newAnswer;
383
+ answerPublisher.publish(undefined);
384
+
385
+ return [true, newAnswer];
386
+ },
387
+ /**
388
+ * @param {bigint} roundId
389
+ * @param {Timestamp} blockTimestamp
390
+ */
391
+ updateTimedOutRoundInfo(roundId, blockTimestamp) {
392
+ const { details, rounds } = this.state;
393
+ const { helper } = this.facets;
394
+
395
+ // round 0 is non-existent, so we avoid that case -- round 1 is ignored
396
+ // because we can't copy from round 0 in that case
397
+ if (roundId === 0n || roundId === 1n) {
398
+ return;
399
+ }
400
+
401
+ const roundTimedOut = helper.timedOut(roundId, blockTimestamp);
402
+ if (!roundTimedOut) return;
403
+
404
+ const prevRound = rounds.get(subtract(roundId, 1));
405
+
406
+ rounds.set(roundId, {
407
+ ...rounds.get(roundId),
408
+ answer: prevRound.answer,
409
+ answeredInRound: prevRound.answeredInRound,
410
+ updatedAt: blockTimestamp,
411
+ });
412
+
413
+ details.delete(roundId);
414
+ },
415
+
416
+ /**
417
+ * @param {OracleStatus} status
418
+ * @param {bigint} roundId
419
+ * @param {Timestamp} blockTimestamp
420
+ * @returns {string?} error message, if there is one
421
+ */
422
+ validateOracleRound(status, roundId, blockTimestamp) {
423
+ const { reportingRoundId } = this.state;
424
+ const { helper } = this.facets;
425
+
426
+ let canSupersede = true;
427
+ if (roundId > 1n) {
428
+ canSupersede = helper.supersedable(
429
+ subtract(roundId, 1),
430
+ blockTimestamp,
431
+ );
432
+ }
433
+
434
+ if (status.lastReportedRound >= roundId)
435
+ return 'cannot report on previous rounds';
436
+ if (
437
+ roundId !== reportingRoundId &&
438
+ roundId !== add(reportingRoundId, 1) &&
439
+ !helper.previousAndCurrentUnanswered(roundId, reportingRoundId)
440
+ )
441
+ return 'invalid round to report';
442
+ if (roundId !== 1n && !canSupersede)
443
+ return 'previous round not supersedable';
444
+ return null;
445
+ },
446
+ },
447
+ contract: {
448
+ /**
449
+ *
450
+ * @param {PriceQuoteValue} quote
451
+ */
452
+ async authenticateQuote(quote) {
453
+ const { quoteKit } = this.state;
454
+ const quoteAmount = AmountMath.make(quoteKit.brand, harden(quote));
455
+ const quotePayment = await E(quoteKit.mint).mintPayment(quoteAmount);
456
+ return harden({ quoteAmount, quotePayment });
457
+ },
458
+
459
+ /**
460
+ * @param {object} param0
461
+ * @param {number} [param0.overrideValueOut]
462
+ * @param {Timestamp} [param0.timestamp]
463
+ */
464
+ makeCreateQuote({ overrideValueOut, timestamp } = {}) {
465
+ const { state } = this;
466
+ const { brandIn, brandOut, timerPresence } = state;
467
+ const { contract } = this.facets;
468
+
469
+ /**
470
+ * @param {PriceQuery} priceQuery
471
+ */
472
+ return Far('createQuote', priceQuery => {
473
+ const { lastValueOutForUnitIn, unitIn } = state;
474
+
475
+ // Sniff the current baseValueOut.
476
+ const valueOutForUnitIn =
477
+ overrideValueOut === undefined
478
+ ? lastValueOutForUnitIn // Use the latest value.
479
+ : overrideValueOut; // Override the value.
480
+ if (valueOutForUnitIn === null) {
481
+ // We don't have a quote, so abort.
482
+ return undefined;
483
+ }
484
+
485
+ /**
486
+ * @param {Amount<'nat'>} amountIn the given amountIn
487
+ */
488
+ const calcAmountOut = amountIn => {
489
+ const valueIn = AmountMath.getValue(brandIn, amountIn);
490
+ return AmountMath.make(
491
+ brandOut,
492
+ floorDivide(multiply(valueIn, valueOutForUnitIn), unitIn),
493
+ );
494
+ };
495
+
496
+ /**
497
+ * @param {Amount<'nat'>} amountOut the wanted amountOut
498
+ */
499
+ const calcAmountIn = amountOut => {
500
+ const valueOut = AmountMath.getValue(brandOut, amountOut);
501
+ return AmountMath.make(
502
+ brandIn,
503
+ ceilDivide(multiply(valueOut, unitIn), valueOutForUnitIn),
504
+ );
505
+ };
506
+
507
+ // Calculate the quote.
508
+ const quote = priceQuery(calcAmountOut, calcAmountIn);
509
+ if (!quote) {
510
+ return undefined;
511
+ }
512
+
513
+ const {
514
+ amountIn,
515
+ amountOut,
516
+ timestamp: theirTimestamp = timestamp,
517
+ } = quote;
518
+ AmountMath.coerce(brandIn, amountIn);
519
+ AmountMath.coerce(brandOut, amountOut);
520
+ if (theirTimestamp !== undefined) {
521
+ return contract.authenticateQuote([
522
+ {
523
+ amountIn,
524
+ amountOut,
525
+ timer: timerPresence,
526
+ timestamp: theirTimestamp,
527
+ },
528
+ ]);
529
+ }
530
+ return E(timerPresence)
531
+ .getCurrentTimestamp()
532
+ .then(now =>
533
+ contract.authenticateQuote([
534
+ { amountIn, amountOut, timer: timerPresence, timestamp: now },
535
+ ]),
536
+ );
537
+ });
538
+ },
539
+
540
+ /**
541
+ * @param {OracleStatus} status
542
+ * @param {bigint} queriedRoundId
543
+ * @param {Timestamp} blockTimestamp
544
+ */
545
+ eligibleForSpecificRound(status, queriedRoundId, blockTimestamp) {
546
+ const { rounds } = this.state;
547
+ const { helper } = this.facets;
548
+ const error = helper.validateOracleRound(
549
+ status,
550
+ queriedRoundId,
551
+ blockTimestamp,
552
+ );
553
+ if (TimeMath.absValue(rounds.get(queriedRoundId).startedAt) > 0n) {
554
+ return (
555
+ helper.acceptingSubmissions(queriedRoundId) && error === null
556
+ );
557
+ } else {
558
+ return helper.delayed(status, queriedRoundId) && error === null;
559
+ }
560
+ },
561
+
562
+ /**
563
+ * consumers are encouraged to check
564
+ * that they're receiving fresh data by inspecting the updatedAt and
565
+ * answeredInRound return values.
566
+ *
567
+ * @param {bigint | number} roundIdRaw
568
+ * @returns {Promise<RoundData>}
569
+ */
570
+ async getRoundData(roundIdRaw) {
571
+ const roundId = Nat(roundIdRaw);
572
+ const { rounds } = this.state;
573
+
574
+ rounds.has(roundId) || assert.fail(V3_NO_DATA_ERROR);
575
+
576
+ const r = rounds.get(roundId);
577
+
578
+ assert(
579
+ r.answeredInRound > 0 && validRoundId(roundId),
580
+ V3_NO_DATA_ERROR,
581
+ );
582
+
583
+ return {
584
+ roundId,
585
+ answer: r.answer,
586
+ startedAt: r.startedAt,
587
+ updatedAt: r.updatedAt,
588
+ answeredInRound: r.answeredInRound,
589
+ };
590
+ },
591
+
592
+ /** @type {(roundId: bigint) => Readonly<RoundDetails & Round>} */
593
+ getRoundStatus(roundId) {
594
+ const { details, rounds } = this.state;
595
+ rounds.has(roundId) || Fail`V3_NO_DATA_ERROR`;
596
+ const detail = details.get(roundId);
597
+ const round = rounds.get(roundId);
598
+ return harden({ ...detail, ...round });
599
+ },
600
+
601
+ /**
602
+ * a method to provide all current info oracleStatuses need. Intended only
603
+ * only to be callable by oracleStatuses. Not for use by contracts to read state.
604
+ *
605
+ * @param {OracleStatus} status
606
+ * @param {Timestamp} blockTimestamp
607
+ */
608
+ oracleRoundStateSuggestRound(status, blockTimestamp) {
609
+ const { helper } = this.facets;
610
+ const { details, reportingRoundId, rounds } = this.state;
611
+ const shouldSupersede =
612
+ status.lastReportedRound === reportingRoundId ||
613
+ !helper.acceptingSubmissions(reportingRoundId);
614
+ // Instead of nudging oracleStatuses to submit to the next round, the inclusion of
615
+ // the shouldSupersede Boolean in the if condition pushes them towards
616
+ // submitting in a currently open round.
617
+ const canSupersede = helper.supersedable(
618
+ reportingRoundId,
619
+ blockTimestamp,
620
+ );
621
+
622
+ let roundId;
623
+ let eligibleToSubmit;
624
+ if (canSupersede && shouldSupersede) {
625
+ roundId = add(reportingRoundId, 1);
626
+ eligibleToSubmit = helper.delayed(status, roundId);
627
+ } else {
628
+ roundId = reportingRoundId;
629
+ eligibleToSubmit = helper.acceptingSubmissions(roundId);
630
+ }
631
+
632
+ let round;
633
+ let startedAt;
634
+ let roundTimeout;
635
+ if (rounds.has(roundId)) {
636
+ round = rounds.get(roundId);
637
+ startedAt = round.startedAt;
638
+ roundTimeout = details.get(roundId).roundTimeout;
639
+ } else {
640
+ startedAt = 0n;
641
+ roundTimeout = 0;
642
+ }
643
+
644
+ const error = helper.validateOracleRound(
645
+ status,
646
+ roundId,
647
+ blockTimestamp,
648
+ );
649
+ if (error !== null) {
650
+ eligibleToSubmit = false;
651
+ }
652
+
653
+ return {
654
+ eligibleForSpecificRound: eligibleToSubmit,
655
+ queriedRoundId: roundId,
656
+ latestSubmission: status.latestSubmission,
657
+ startedAt,
658
+ roundTimeout,
659
+ };
660
+ },
661
+ },
662
+ oracle: {
663
+ /**
664
+ * push a unitPrice result from this oracle
665
+ *
666
+ * @param {OracleStatus} status
667
+ * @param {PriceRound} result
668
+ */
669
+ async handlePush(
670
+ status,
671
+ { roundId: roundIdRaw = undefined, unitPrice: valueRaw },
672
+ ) {
673
+ const value = Nat(valueRaw);
674
+ const { minSubmissionValue, maxSubmissionValue, timerPresence } =
675
+ this.state;
676
+
677
+ const { contract, helper } = this.facets;
678
+ value >= minSubmissionValue ||
679
+ Fail`value below minSubmissionValue ${q(minSubmissionValue)}`;
680
+ value <= maxSubmissionValue ||
681
+ Fail`value above maxSubmissionValue ${q(maxSubmissionValue)}`;
682
+
683
+ const blockTimestamp = await E(timerPresence).getCurrentTimestamp();
684
+
685
+ let roundId;
686
+ if (roundIdRaw === undefined) {
687
+ const suggestedRound = contract.oracleRoundStateSuggestRound(
688
+ status,
689
+ blockTimestamp,
690
+ );
691
+ roundId = suggestedRound.eligibleForSpecificRound
692
+ ? suggestedRound.queriedRoundId
693
+ : add(suggestedRound.queriedRoundId, 1);
694
+ } else {
695
+ roundId = Nat(roundIdRaw);
696
+ }
697
+
698
+ const errorMsg = helper.validateOracleRound(
699
+ status,
700
+ roundId,
701
+ blockTimestamp,
702
+ );
703
+
704
+ if (!(errorMsg === null)) {
705
+ assert.fail(errorMsg);
706
+ }
707
+
708
+ const proposedStatus = helper.proposeNewRound(
709
+ roundId,
710
+ status,
711
+ blockTimestamp,
712
+ );
713
+ const settledStatus = helper.recordSubmission(
714
+ value,
715
+ roundId,
716
+ proposedStatus || status,
717
+ );
718
+
719
+ helper.updateRoundAnswer(roundId, blockTimestamp);
720
+ helper.deleteRoundDetails(roundId);
721
+
722
+ return settledStatus;
723
+ },
724
+ },
725
+ },
726
+ );