@agoric/inter-protocol 0.13.3 → 0.13.4-dev-800c2bb.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -20
- package/bundles/bundle-auctioneer-js-meta.json +722 -0
- package/bundles/bundle-auctioneer.js +1 -0
- package/bundles/bundle-econCommitteeCharter-js-meta.json +514 -0
- package/bundles/bundle-econCommitteeCharter.js +1 -0
- package/bundles/bundle-feeDistributor-js-meta.json +458 -0
- package/bundles/bundle-feeDistributor.js +1 -1
- package/bundles/bundle-fluxAggregatorKit-js-meta.json +674 -0
- package/bundles/bundle-fluxAggregatorKit.js +1 -0
- package/bundles/bundle-psm-js-meta.json +666 -0
- package/bundles/bundle-psm.js +1 -1
- package/bundles/bundle-reserve-js-meta.json +662 -0
- package/bundles/bundle-reserve.js +1 -1
- package/bundles/bundle-scaledPriceAuthority-js-meta.json +542 -0
- package/bundles/bundle-scaledPriceAuthority.js +1 -1
- package/bundles/bundle-vaultFactory-js-meta.json +786 -0
- package/bundles/bundle-vaultFactory.js +1 -1
- package/exported.js +0 -2
- package/package.json +34 -31
- package/scripts/add-collateral-core.js +14 -4
- package/scripts/build-bundles.js +9 -2
- package/scripts/deploy-contracts.js +1 -4
- package/scripts/init-core.js +20 -38
- package/scripts/invite-committee-core.js +1 -1
- package/scripts/manual-price-feed.js +2 -2
- package/scripts/price-feed-core.js +7 -6
- package/scripts/start-local-chain.sh +84 -0
- package/src/auction/auctionBook.js +755 -0
- package/src/auction/auctioneer.js +720 -0
- package/src/auction/offerBook.js +221 -0
- package/src/auction/params.js +174 -0
- package/src/auction/scheduleMath.js +167 -0
- package/src/auction/scheduler.js +370 -0
- package/src/auction/sortedOffers.js +136 -0
- package/src/auction/util.js +55 -0
- package/src/clientSupport.js +324 -0
- package/src/collectFees.js +8 -14
- package/src/contractSupport.js +24 -68
- package/src/econCommitteeCharter.js +161 -77
- package/src/feeDistributor.js +60 -20
- package/src/index.js +2 -0
- package/src/interest-math.js +1 -4
- package/src/interest.js +20 -13
- package/src/price/README.md +57 -0
- package/src/price/fluxAggregatorContract.js +190 -0
- package/src/price/fluxAggregatorKit.js +375 -0
- package/src/price/priceOracleKit.js +127 -0
- package/src/price/roundsManager.js +726 -0
- package/src/proposals/addAssetToVault.js +89 -94
- package/src/proposals/committee-proposal.js +141 -40
- package/src/proposals/core-proposal.js +100 -167
- package/src/proposals/econ-behaviors.js +320 -564
- package/src/proposals/price-feed-proposal.js +175 -78
- package/src/proposals/startEconCommittee.js +25 -12
- package/src/proposals/startPSM.js +231 -211
- package/src/proposals/utils.js +31 -17
- package/src/psm/README.md +71 -0
- package/src/psm/psm.js +272 -160
- package/src/psm/types.js +2 -0
- package/src/reserve/assetReserve.js +56 -413
- package/src/reserve/assetReserveKit.js +303 -0
- package/src/reserve/doc.md +1 -3
- package/src/reserve/params.js +2 -5
- package/src/typeGuards.js +13 -0
- package/src/vaultFactory/Liquidation.md +100 -0
- package/src/vaultFactory/burn.js +2 -1
- package/src/vaultFactory/liquidation.js +270 -108
- package/src/vaultFactory/math.js +95 -0
- package/src/vaultFactory/orderedVaultStore.js +0 -1
- package/src/vaultFactory/params.js +133 -112
- package/src/vaultFactory/prioritizedVaults.js +36 -79
- package/src/vaultFactory/storeUtils.js +67 -9
- package/src/vaultFactory/type-imports.js +2 -0
- package/src/vaultFactory/types.js +22 -26
- package/src/vaultFactory/vault.js +772 -709
- package/src/vaultFactory/vaultDirector.js +480 -483
- package/src/vaultFactory/vaultFactory.js +99 -60
- package/src/vaultFactory/vaultHolder.js +121 -95
- package/src/vaultFactory/vaultKit.js +39 -31
- package/src/vaultFactory/vaultManager.js +1249 -868
- package/bundles/bundle-amm-meta.js +0 -618
- package/bundles/bundle-amm.js +0 -1
- package/bundles/bundle-feeDistributor-meta.js +0 -346
- package/bundles/bundle-interchainPool-meta.js +0 -534
- package/bundles/bundle-interchainPool.js +0 -1
- package/bundles/bundle-liquidateIncrementally-meta.js +0 -442
- package/bundles/bundle-liquidateIncrementally.js +0 -1
- package/bundles/bundle-liquidateMinimum-meta.js +0 -442
- package/bundles/bundle-liquidateMinimum.js +0 -1
- package/bundles/bundle-psm-meta.js +0 -538
- package/bundles/bundle-psmCharter-meta.js +0 -418
- package/bundles/bundle-psmCharter.js +0 -1
- package/bundles/bundle-reserve-meta.js +0 -542
- package/bundles/bundle-scaledPriceAuthority-meta.js +0 -442
- package/bundles/bundle-stakeFactory-meta.js +0 -574
- package/bundles/bundle-stakeFactory.js +0 -1
- package/bundles/bundle-vaultFactory-meta.js +0 -598
- package/scripts/add-initial-liquidity.js +0 -180
- package/src/collect.js +0 -13
- package/src/interchainPool.js +0 -135
- package/src/makeTracer.js +0 -36
- package/src/my-lien.js +0 -85
- package/src/proposals/demoIssuers.js +0 -685
- package/src/proposals/sim-behaviors.js +0 -40
- package/src/psm/psmCharter.js +0 -139
- package/src/stakeFactory/README.md +0 -62
- package/src/stakeFactory/attestation.js +0 -267
- package/src/stakeFactory/attestationTool.js +0 -66
- package/src/stakeFactory/constants.js +0 -16
- package/src/stakeFactory/params.js +0 -31
- package/src/stakeFactory/stakeFactory.js +0 -236
- package/src/stakeFactory/stakeFactoryKit.js +0 -455
- package/src/stakeFactory/stakeFactoryManager.js +0 -296
- package/src/stakeFactory/types.js +0 -42
- package/src/vaultFactory/liquidateIncrementally.js +0 -350
- package/src/vaultFactory/liquidateMinimum.js +0 -100
- package/src/vpool-xyk-amm/AttackersGuide.md +0 -89
- package/src/vpool-xyk-amm/addLiquidity.js +0 -222
- package/src/vpool-xyk-amm/addPool.js +0 -197
- package/src/vpool-xyk-amm/constantProduct/AMM-Trade.jpeg +0 -0
- package/src/vpool-xyk-amm/constantProduct/README.md +0 -204
- package/src/vpool-xyk-amm/constantProduct/calcFees.js +0 -99
- package/src/vpool-xyk-amm/constantProduct/calcSwapPrices.js +0 -85
- package/src/vpool-xyk-amm/constantProduct/core.js +0 -153
- package/src/vpool-xyk-amm/constantProduct/defaults.js +0 -5
- package/src/vpool-xyk-amm/constantProduct/getXY.js +0 -52
- package/src/vpool-xyk-amm/constantProduct/internal-types.js +0 -116
- package/src/vpool-xyk-amm/constantProduct/invariants.js +0 -43
- package/src/vpool-xyk-amm/constantProduct/swap.js +0 -272
- package/src/vpool-xyk-amm/constantProduct/types.js +0 -32
- package/src/vpool-xyk-amm/doublePool.js +0 -194
- package/src/vpool-xyk-amm/estimate.js +0 -155
- package/src/vpool-xyk-amm/multipoolMarketMaker.js +0 -520
- package/src/vpool-xyk-amm/params.js +0 -61
- package/src/vpool-xyk-amm/pool.js +0 -396
- package/src/vpool-xyk-amm/priceAuthority.js +0 -94
- package/src/vpool-xyk-amm/removeLiquidity.js +0 -31
- package/src/vpool-xyk-amm/singlePool.js +0 -73
- package/src/vpool-xyk-amm/swap.js +0 -69
- 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
|
+
);
|