@agoric/inter-protocol 0.13.3 → 0.13.4-dev-d32a66a.0

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