@gearbox-protocol/sdk 18.2.0-next.13 → 18.2.0-next.14
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/dist/cjs/permissionless/api/index.js +2 -1
- package/dist/cjs/permissionless/api/oracles/PermissionlessOracles.js +40 -5
- package/dist/cjs/permissionless/api/oracles/index.js +2 -1
- package/dist/cjs/permissionless/api/oracles/score/formula.js +19 -24
- package/dist/cjs/permissionless/api/oracles/score/index.js +1 -1
- package/dist/cjs/permissionless/api/oracles/score/run.js +186 -105
- package/dist/cjs/permissionless/api/oracles/stress/index.js +1 -0
- package/dist/cjs/permissionless/api/oracles/stress/run.js +63 -13
- package/dist/cjs/permissionless/index.js +2 -1
- package/dist/esm/permissionless/api/index.js +4 -4
- package/dist/esm/permissionless/api/oracles/PermissionlessOracles.js +41 -6
- package/dist/esm/permissionless/api/oracles/index.js +4 -4
- package/dist/esm/permissionless/api/oracles/score/formula.js +20 -24
- package/dist/esm/permissionless/api/oracles/score/index.js +3 -3
- package/dist/esm/permissionless/api/oracles/score/run.js +187 -107
- package/dist/esm/permissionless/api/oracles/stress/index.js +2 -2
- package/dist/esm/permissionless/api/oracles/stress/run.js +64 -15
- package/dist/esm/permissionless/index.js +4 -4
- package/dist/types/permissionless/api/index.d.ts +5 -5
- package/dist/types/permissionless/api/oracles/PermissionlessOracles.d.ts +30 -6
- package/dist/types/permissionless/api/oracles/index.d.ts +5 -5
- package/dist/types/permissionless/api/oracles/score/formula.d.ts +14 -17
- package/dist/types/permissionless/api/oracles/score/index.d.ts +4 -4
- package/dist/types/permissionless/api/oracles/score/run.d.ts +28 -10
- package/dist/types/permissionless/api/oracles/score/types.d.ts +33 -28
- package/dist/types/permissionless/api/oracles/stress/index.d.ts +2 -2
- package/dist/types/permissionless/api/oracles/stress/run.d.ts +11 -0
- package/dist/types/permissionless/index.d.ts +5 -5
- package/package.json +1 -1
|
@@ -35,7 +35,6 @@ exports.PriceFeedNotInStoreError = require_permissionless_api_errors.PriceFeedNo
|
|
|
35
35
|
exports.PriceFeedSaltMissingError = require_permissionless_api_errors.PriceFeedSaltMissingError;
|
|
36
36
|
exports.PriceFeedStalenessUnknownError = require_permissionless_api_errors.PriceFeedStalenessUnknownError;
|
|
37
37
|
exports.RATE_FEED_TYPE = require_permissionless_api_oracles_stress_run.RATE_FEED_TYPE;
|
|
38
|
-
exports.REVERT_SCORE = require_permissionless_api_oracles_score_formula.REVERT_SCORE;
|
|
39
38
|
exports.SCORE_MULTIPLIER = require_permissionless_api_oracles_score_formula.SCORE_MULTIPLIER;
|
|
40
39
|
exports.UNBOUNDED_DOWN = require_permissionless_api_oracles_stress_override.UNBOUNDED_DOWN;
|
|
41
40
|
exports.UNBOUNDED_UP = require_permissionless_api_oracles_stress_override.UNBOUNDED_UP;
|
|
@@ -47,11 +46,13 @@ exports.groupOverride = require_permissionless_api_oracles_stress_override.group
|
|
|
47
46
|
exports.perturb = require_permissionless_api_oracles_stress_override.perturb;
|
|
48
47
|
exports.priceFeedSalt = require_permissionless_api_deploy_PermissionlessDeploy.priceFeedSalt;
|
|
49
48
|
exports.rateStub = require_permissionless_api_oracles_stress_override.rateStub;
|
|
49
|
+
exports.readFeedTrees = require_permissionless_api_oracles_stress_run.readFeedTrees;
|
|
50
50
|
exports.readForest = require_permissionless_api_oracles_stress_run.readForest;
|
|
51
51
|
exports.readRateTargets = require_permissionless_api_oracles_stress_run.readRateTargets;
|
|
52
52
|
exports.readUnder = require_permissionless_api_oracles_stress_run.readUnder;
|
|
53
53
|
exports.roundDataStub = require_permissionless_api_oracles_stress_override.roundDataStub;
|
|
54
54
|
exports.runAssetScores = require_permissionless_api_oracles_score_run.runAssetScores;
|
|
55
|
+
exports.runFeedPairScore = require_permissionless_api_oracles_score_run.runFeedPairScore;
|
|
55
56
|
exports.runStressTest = require_permissionless_api_oracles_stress_run.runStressTest;
|
|
56
57
|
exports.scoreGroup = require_permissionless_api_oracles_stress_score.scoreGroup;
|
|
57
58
|
exports.scoreOfFactor = require_permissionless_api_oracles_score_formula.scoreOfFactor;
|
|
@@ -360,14 +360,16 @@ var PermissionlessOracles = class extends require_offchain_AbstractOffchainNames
|
|
|
360
360
|
* three leaves anything to simulate.
|
|
361
361
|
**/
|
|
362
362
|
/**
|
|
363
|
-
* Every asset's
|
|
363
|
+
* Every asset's manipulation resistance: how far, from 1 to 10 times, a
|
|
364
|
+
* lying source can push the price its markets hold for it.
|
|
364
365
|
*
|
|
365
366
|
* Each source group behind an asset's feeds — Chainlink, Redstone, Lido and
|
|
366
|
-
* so on — is made in turn to answer
|
|
367
|
+
* so on — is made in turn to answer ten times what it answers now, and a
|
|
367
368
|
* market scores by how far that moves its final price, `min(main,
|
|
368
|
-
* reserve)
|
|
369
|
-
*
|
|
370
|
-
*
|
|
369
|
+
* reserve)`, as a multiple either way: 1 for not at all, 1.04 for 4%, 10
|
|
370
|
+
* for the whole lie. A group under which the price stops answering moves
|
|
371
|
+
* nothing and is listed rather than scored. An asset scores its worst
|
|
372
|
+
* running market, underlying or collateral.
|
|
371
373
|
*
|
|
372
374
|
* Simulated on the chain as it stands, through state overrides in
|
|
373
375
|
* `eth_call`: one call per source group per chain, so the node behind the
|
|
@@ -409,6 +411,39 @@ var PermissionlessOracles = class extends require_offchain_AbstractOffchainNames
|
|
|
409
411
|
}
|
|
410
412
|
}))).flat();
|
|
411
413
|
}
|
|
414
|
+
/**
|
|
415
|
+
* The manipulation resistance of any main feed and reserve on a chain,
|
|
416
|
+
* scored the way {@link assetScores} scores a market holding them — for
|
|
417
|
+
* trying a pair before any market does.
|
|
418
|
+
*
|
|
419
|
+
* ```ts
|
|
420
|
+
* const pair = await permissionless.oracles.feedPairScore({
|
|
421
|
+
* chainId: 1,
|
|
422
|
+
* main: "0x…",
|
|
423
|
+
* reserve: "0x…",
|
|
424
|
+
* });
|
|
425
|
+
* ```
|
|
426
|
+
*
|
|
427
|
+
* Reads the chain directly, through state overrides in `eth_call`, so the
|
|
428
|
+
* client has to have attached and the node behind it has to accept them.
|
|
429
|
+
*
|
|
430
|
+
* @throws When the client has not attached, when the chain has no client or
|
|
431
|
+
* no PriceFeedCompressor, or when there is nothing to score: a feed the
|
|
432
|
+
* compressor cannot read, a `ZeroPriceFeed` main, or a final price that does
|
|
433
|
+
* not answer.
|
|
434
|
+
**/
|
|
435
|
+
async feedPairScore({ chainId, main, reserve }) {
|
|
436
|
+
const client = this.#chains.client(chainId);
|
|
437
|
+
const compressor = await new require_permissionless_bindings_address_provider.AddressProviderContract("0xF7f0a609BfAb9a0A98786951ef10e5FE26cC1E38", client).getLatestVersionAddress(require_onchain_constants_address_provider.AP_PRICE_FEED_COMPRESSOR);
|
|
438
|
+
return require_permissionless_api_oracles_score_run.runFeedPairScore({
|
|
439
|
+
client,
|
|
440
|
+
chainId,
|
|
441
|
+
compressor,
|
|
442
|
+
main,
|
|
443
|
+
reserve,
|
|
444
|
+
logger: this.logger ?? void 0
|
|
445
|
+
});
|
|
446
|
+
}
|
|
412
447
|
async stressTest({ chainId, ...options }) {
|
|
413
448
|
const client = this.#chains.client(chainId);
|
|
414
449
|
const compressor = await new require_permissionless_bindings_address_provider.AddressProviderContract("0xF7f0a609BfAb9a0A98786951ef10e5FE26cC1E38", client).getLatestVersionAddress(require_onchain_constants_address_provider.AP_PRICE_FEED_COMPRESSOR);
|
|
@@ -14,7 +14,6 @@ exports.GET_PRICES_CHUNK_SIZE = require_permissionless_api_oracles_Permissionles
|
|
|
14
14
|
exports.GET_PRICES_GAS_LIMIT_BY_CHAIN_ID = require_permissionless_api_oracles_PermissionlessOracles.GET_PRICES_GAS_LIMIT_BY_CHAIN_ID;
|
|
15
15
|
exports.PermissionlessOracles = require_permissionless_api_oracles_PermissionlessOracles.PermissionlessOracles;
|
|
16
16
|
exports.RATE_FEED_TYPE = require_permissionless_api_oracles_stress_run.RATE_FEED_TYPE;
|
|
17
|
-
exports.REVERT_SCORE = require_permissionless_api_oracles_score_formula.REVERT_SCORE;
|
|
18
17
|
exports.SCORE_MULTIPLIER = require_permissionless_api_oracles_score_formula.SCORE_MULTIPLIER;
|
|
19
18
|
exports.UNBOUNDED_DOWN = require_permissionless_api_oracles_stress_override.UNBOUNDED_DOWN;
|
|
20
19
|
exports.UNBOUNDED_UP = require_permissionless_api_oracles_stress_override.UNBOUNDED_UP;
|
|
@@ -24,11 +23,13 @@ exports.finalPrice = require_permissionless_api_oracles_score_formula.finalPrice
|
|
|
24
23
|
exports.groupOverride = require_permissionless_api_oracles_stress_override.groupOverride;
|
|
25
24
|
exports.perturb = require_permissionless_api_oracles_stress_override.perturb;
|
|
26
25
|
exports.rateStub = require_permissionless_api_oracles_stress_override.rateStub;
|
|
26
|
+
exports.readFeedTrees = require_permissionless_api_oracles_stress_run.readFeedTrees;
|
|
27
27
|
exports.readForest = require_permissionless_api_oracles_stress_run.readForest;
|
|
28
28
|
exports.readRateTargets = require_permissionless_api_oracles_stress_run.readRateTargets;
|
|
29
29
|
exports.readUnder = require_permissionless_api_oracles_stress_run.readUnder;
|
|
30
30
|
exports.roundDataStub = require_permissionless_api_oracles_stress_override.roundDataStub;
|
|
31
31
|
exports.runAssetScores = require_permissionless_api_oracles_score_run.runAssetScores;
|
|
32
|
+
exports.runFeedPairScore = require_permissionless_api_oracles_score_run.runFeedPairScore;
|
|
32
33
|
exports.runStressTest = require_permissionless_api_oracles_stress_run.runStressTest;
|
|
33
34
|
exports.scoreGroup = require_permissionless_api_oracles_stress_score.scoreGroup;
|
|
34
35
|
exports.scoreOfFactor = require_permissionless_api_oracles_score_formula.scoreOfFactor;
|
|
@@ -1,26 +1,20 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
//#region src/permissionless/api/oracles/score/formula.ts
|
|
3
3
|
/** What every source of a group is made to answer, as a multiple of now. */
|
|
4
|
-
const SCORE_MULTIPLIER =
|
|
4
|
+
const SCORE_MULTIPLIER = 10;
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
6
|
+
* How far the final price moved, as a multiple of where it was, either way:
|
|
7
|
+
* 1 for a price that did not move, 1.04 for one 4% off, and
|
|
8
|
+
* {@link SCORE_MULTIPLIER} for one that took the whole lie — the most it can
|
|
9
|
+
* take from one leg, and where a price over two legs of one group, moved
|
|
10
|
+
* further, is held too.
|
|
7
11
|
*
|
|
8
|
-
*
|
|
9
|
-
* liquidated either until the feed is replaced.
|
|
10
|
-
*/
|
|
11
|
-
const REVERT_SCORE = 5;
|
|
12
|
-
/**
|
|
13
|
-
* 0 to 10 by how far the final price moved, on a log scale against the lie
|
|
14
|
-
* itself: 10 for a price that did not move, 0 for one that moved by the full
|
|
15
|
-
* {@link SCORE_MULTIPLIER} or more, either way. Rounded to a half point.
|
|
16
|
-
*
|
|
17
|
-
* A bound holding a price within a few percent keeps a 10; a price that let
|
|
18
|
-
* half the lie through, ×√5, scores 5.
|
|
12
|
+
* `null` for a price that stopped answering, which is not a move.
|
|
19
13
|
*/
|
|
20
14
|
function scoreOfFactor(factor) {
|
|
21
|
-
if (factor === null || !Number.isFinite(factor) || factor <= 0) return
|
|
22
|
-
const
|
|
23
|
-
return Math.
|
|
15
|
+
if (factor === null || !Number.isFinite(factor) || factor <= 0) return null;
|
|
16
|
+
const move = factor >= 1 ? factor : 1 / factor;
|
|
17
|
+
return Math.min(10, move);
|
|
24
18
|
}
|
|
25
19
|
/**
|
|
26
20
|
* The price a market settles on: the lower of main and reserve, or the main
|
|
@@ -38,22 +32,23 @@ function factorOf(base, stressed) {
|
|
|
38
32
|
return Number(stressed) / Number(base);
|
|
39
33
|
}
|
|
40
34
|
/**
|
|
41
|
-
* The market's score and the group it comes from: the
|
|
42
|
-
*
|
|
43
|
-
* feeds the price at all
|
|
35
|
+
* The market's score and the group it comes from: the furthest any group
|
|
36
|
+
* moved its price. 1 where nothing moved it — no group did, or no source
|
|
37
|
+
* feeds the price at all.
|
|
38
|
+
*
|
|
39
|
+
* A group under which the price stops answering moves nothing and does not
|
|
40
|
+
* set the score; it is listed after those that do, for a reader to see.
|
|
44
41
|
*/
|
|
45
42
|
function worstOf(impacts) {
|
|
46
|
-
const
|
|
47
|
-
const
|
|
48
|
-
const worst = sorted[0] ?? null;
|
|
43
|
+
const sorted = [...impacts].sort((a, b) => (b.score ?? 0) - (a.score ?? 0));
|
|
44
|
+
const worst = sorted.find((impact) => impact.score !== null) ?? null;
|
|
49
45
|
return {
|
|
50
|
-
score: worst?.score ??
|
|
46
|
+
score: worst?.score ?? 1,
|
|
51
47
|
impacts: sorted,
|
|
52
48
|
worst
|
|
53
49
|
};
|
|
54
50
|
}
|
|
55
51
|
//#endregion
|
|
56
|
-
exports.REVERT_SCORE = REVERT_SCORE;
|
|
57
52
|
exports.SCORE_MULTIPLIER = SCORE_MULTIPLIER;
|
|
58
53
|
exports.factorOf = factorOf;
|
|
59
54
|
exports.finalPrice = finalPrice;
|
|
@@ -2,10 +2,10 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
|
2
2
|
const require_permissionless_api_oracles_score_formula = require("./formula.js");
|
|
3
3
|
const require_permissionless_api_oracles_score_run = require("./run.js");
|
|
4
4
|
require("./types.js");
|
|
5
|
-
exports.REVERT_SCORE = require_permissionless_api_oracles_score_formula.REVERT_SCORE;
|
|
6
5
|
exports.SCORE_MULTIPLIER = require_permissionless_api_oracles_score_formula.SCORE_MULTIPLIER;
|
|
7
6
|
exports.factorOf = require_permissionless_api_oracles_score_formula.factorOf;
|
|
8
7
|
exports.finalPrice = require_permissionless_api_oracles_score_formula.finalPrice;
|
|
9
8
|
exports.runAssetScores = require_permissionless_api_oracles_score_run.runAssetScores;
|
|
9
|
+
exports.runFeedPairScore = require_permissionless_api_oracles_score_run.runFeedPairScore;
|
|
10
10
|
exports.scoreOfFactor = require_permissionless_api_oracles_score_formula.scoreOfFactor;
|
|
11
11
|
exports.worstOf = require_permissionless_api_oracles_score_formula.worstOf;
|
|
@@ -7,58 +7,67 @@ let viem = require("viem");
|
|
|
7
7
|
const ZERO_TYPE = "PRICE_FEED::ZERO";
|
|
8
8
|
/** Groups simulated at once: one `eth_call` each, all against one node. */
|
|
9
9
|
const CONCURRENCY = 6;
|
|
10
|
-
const
|
|
10
|
+
const lower = (address) => address.toLowerCase();
|
|
11
11
|
/**
|
|
12
|
-
*
|
|
13
|
-
*
|
|
12
|
+
* A main feed and the reserve a slot or a caller names for it, as the score
|
|
13
|
+
* takes them: a reserve that is a `ZeroPriceFeed` is no reserve.
|
|
14
|
+
*/
|
|
15
|
+
function pairOf(main, reserve, nodes) {
|
|
16
|
+
const notes = [];
|
|
17
|
+
if (!reserve) {
|
|
18
|
+
notes.push("no reserve feed");
|
|
19
|
+
return {
|
|
20
|
+
main,
|
|
21
|
+
reserve: void 0,
|
|
22
|
+
notes
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
if (nodes.get(reserve)?.contractType === ZERO_TYPE) {
|
|
26
|
+
notes.push("reserve is a ZeroPriceFeed");
|
|
27
|
+
return {
|
|
28
|
+
main,
|
|
29
|
+
reserve: void 0,
|
|
30
|
+
notes
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
if (reserve === main) notes.push("reserve is the main feed");
|
|
34
|
+
return {
|
|
35
|
+
main,
|
|
36
|
+
reserve,
|
|
37
|
+
notes
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Every pair's final price under each source group lying, from one read of
|
|
42
|
+
* the LP feeds' rate contracts, one of every feed as it stands, and then one
|
|
43
|
+
* `eth_call` per source group with the group's sources overridden to answer
|
|
44
|
+
* {@link SCORE_MULTIPLIER} times what they answer now.
|
|
14
45
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
* read under it.
|
|
20
|
-
**/
|
|
21
|
-
async function runAssetScores({ client, chainId, slots, compressor, logger }) {
|
|
22
|
-
const running = slots.filter((slot) => !slot.isShutdown);
|
|
23
|
-
const oracles = [...new Set(running.map((slot) => slot.priceOracle.toLowerCase()))];
|
|
24
|
-
const { nodes, answers, unreadableOracles } = await require_permissionless_api_oracles_stress_run.readForest(client, compressor, oracles);
|
|
25
|
-
for (const oracle of unreadableOracles) logger?.warn(`price oracle ${oracle} of chain ${chainId} would not report its feed tree; the markets on it are not scored`);
|
|
46
|
+
* A pair whose final price does not answer as it stands comes back `null`:
|
|
47
|
+
* there is no price to move.
|
|
48
|
+
*/
|
|
49
|
+
async function simulatePairs({ client, chainId, nodes, answers, pairs, logger }) {
|
|
26
50
|
const { targets, baselines: rateBaselines } = await require_permissionless_api_oracles_stress_run.readRateTargets(client, [...nodes.values()].filter((node) => require_permissionless_api_oracles_stress_run.RATE_FEED_TYPE.test(node.contractType)).map((node) => node.address));
|
|
27
51
|
const { sources, unresolved } = require_permissionless_api_oracles_stress_classify.classifySources(chainId, [...nodes.values()], targets, (target) => rateBaselines.get(target) ?? answers.get(target) ?? null);
|
|
28
52
|
for (const node of unresolved) logger?.warn(`feed ${node.address} of chain ${chainId} would not say what it reads its rate from; its protocol is not perturbed`);
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
if (!main || main.isUnderlying) continue;
|
|
39
|
-
const mainNode = nodes.get(feedOf(main));
|
|
40
|
-
if (!mainNode || mainNode.contractType === ZERO_TYPE) continue;
|
|
41
|
-
const notes = [];
|
|
42
|
-
let reserveFeed;
|
|
43
|
-
if (!reserve) notes.push("no reserve feed");
|
|
44
|
-
else if (nodes.get(feedOf(reserve))?.contractType === ZERO_TYPE) notes.push("reserve is a ZeroPriceFeed");
|
|
45
|
-
else {
|
|
46
|
-
reserveFeed = feedOf(reserve);
|
|
47
|
-
if (reserveFeed === feedOf(main)) notes.push("reserve is the main feed");
|
|
53
|
+
const roots = [...new Set(pairs.flatMap(({ main, reserve }) => reserve ? [main, reserve] : [main]))];
|
|
54
|
+
const reach = require_permissionless_api_oracles_stress_classify.sourcesByFeed(roots, nodes, sources);
|
|
55
|
+
const sourceByTarget = new Map(sources.map((source) => [source.target, source]));
|
|
56
|
+
const drivable = (source) => source !== void 0 && source.perturbable && source.baseline !== null;
|
|
57
|
+
const groupsOf = (feed) => {
|
|
58
|
+
const out = /* @__PURE__ */ new Map();
|
|
59
|
+
for (const target of feed ? reach.get(feed) ?? [] : []) {
|
|
60
|
+
const source = sourceByTarget.get(target);
|
|
61
|
+
if (drivable(source)) out.set(source.group, [...out.get(source.group) ?? [], source]);
|
|
48
62
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
56
|
-
const priced = [...new Set(positions.flatMap(({ main, reserveFeed }) => reserveFeed ? [feedOf(main), reserveFeed] : [feedOf(main)]))];
|
|
57
|
-
if (priced.length === 0) return [];
|
|
58
|
-
const underlyingOf = (slot) => slot.underlyingPriceFeed?.toLowerCase();
|
|
59
|
-
const roots = [.../* @__PURE__ */ new Set([...priced, ...positions.map(({ main }) => underlyingOf(main)).filter((feed) => feed !== void 0)])];
|
|
63
|
+
return out;
|
|
64
|
+
};
|
|
65
|
+
const sourcesOf = (feed) => [...groupsOf(feed).keys()];
|
|
66
|
+
if (roots.length === 0) return {
|
|
67
|
+
results: [],
|
|
68
|
+
sourcesOf
|
|
69
|
+
};
|
|
60
70
|
const updatedAt = BigInt(Math.floor(Date.now() / 1e3));
|
|
61
|
-
const drivable = (source) => source !== void 0 && source.perturbable && source.baseline !== null;
|
|
62
71
|
const fresh = sources.filter((source) => drivable(source) && source.kind === "feed");
|
|
63
72
|
const asIs = await require_permissionless_api_oracles_stress_run.readUnder(client, roots, [], 1, updatedAt);
|
|
64
73
|
const stale = roots.filter((root) => (asIs.get(root) ?? null) === null);
|
|
@@ -68,72 +77,105 @@ async function runAssetScores({ client, chainId, slots, compressor, logger }) {
|
|
|
68
77
|
const freshRoots = stale.filter((root) => (freshened.get(root) ?? null) !== null);
|
|
69
78
|
/** Every root under a lie, each read the way its base was. */
|
|
70
79
|
const readLying = async (members) => {
|
|
71
|
-
const [plain, refreshed] = await Promise.all([asIsRoots.length > 0 ? require_permissionless_api_oracles_stress_run.readUnder(client, asIsRoots, members,
|
|
80
|
+
const [plain, refreshed] = await Promise.all([asIsRoots.length > 0 ? require_permissionless_api_oracles_stress_run.readUnder(client, asIsRoots, members, 10, updatedAt) : /* @__PURE__ */ new Map(), freshRoots.length > 0 ? require_permissionless_api_oracles_stress_run.readUnder(client, freshRoots, members, 10, updatedAt, fresh) : /* @__PURE__ */ new Map()]);
|
|
72
81
|
return new Map([...plain, ...refreshed]);
|
|
73
82
|
};
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
const sourceByTarget = new Map(sources.map((source) => [source.target, source]));
|
|
83
|
+
const effective = pairs.map((pair) => pair.reserve && (base.get(pair.reserve) ?? null) === null ? {
|
|
84
|
+
...pair,
|
|
85
|
+
reserve: void 0,
|
|
86
|
+
notes: [...pair.notes, "reserve does not answer"]
|
|
87
|
+
} : pair);
|
|
80
88
|
const byGroup = /* @__PURE__ */ new Map();
|
|
81
89
|
for (const source of sources.filter(drivable)) byGroup.set(source.group, [...byGroup.get(source.group) ?? [], source]);
|
|
82
90
|
const reachedGroups = /* @__PURE__ */ new Set();
|
|
83
|
-
for (const root of
|
|
84
|
-
const source = sourceByTarget.get(target);
|
|
85
|
-
if (drivable(source)) reachedGroups.add(source.group);
|
|
86
|
-
}
|
|
91
|
+
for (const root of roots) for (const group of groupsOf(root).keys()) reachedGroups.add(group);
|
|
87
92
|
const stressed = /* @__PURE__ */ new Map();
|
|
88
93
|
const queue = [...reachedGroups];
|
|
89
94
|
for (let i = 0; i < queue.length; i += CONCURRENCY) await Promise.all(queue.slice(i, i + CONCURRENCY).map(async (group) => {
|
|
90
95
|
stressed.set(group, await readLying(byGroup.get(group) ?? []));
|
|
91
96
|
}));
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
const after = require_permissionless_api_oracles_score_formula.finalPrice(under.get(mainFeed) ?? null, reserveFeed ? under.get(reserveFeed) ?? null : void 0);
|
|
113
|
-
const factor = require_permissionless_api_oracles_score_formula.factorOf(before, after);
|
|
114
|
-
const underlying = underlyingOf(main);
|
|
115
|
-
const debtBefore = underlying ? base.get(underlying) ?? null : null;
|
|
116
|
-
const debtFactor = debtBefore === null || debtBefore <= 0n || !underlying ? null : require_permissionless_api_oracles_score_formula.factorOf(debtBefore, under.get(underlying) ?? null);
|
|
117
|
-
const ratioFactor = factor === null || debtFactor === null || debtFactor === 0 ? null : factor / debtFactor;
|
|
118
|
-
const members = [...onMain.get(group) ?? [], ...onReserve.get(group) ?? []];
|
|
97
|
+
return {
|
|
98
|
+
results: effective.map(({ main, reserve, notes }) => {
|
|
99
|
+
const before = require_permissionless_api_oracles_score_formula.finalPrice(base.get(main) ?? null, reserve ? base.get(reserve) ?? null : void 0);
|
|
100
|
+
if (before === null || before <= 0n) return null;
|
|
101
|
+
const onMain = groupsOf(main);
|
|
102
|
+
const onReserve = groupsOf(reserve);
|
|
103
|
+
const impacts = [.../* @__PURE__ */ new Set([...onMain.keys(), ...onReserve.keys()])].map((group) => {
|
|
104
|
+
const under = stressed.get(group) ?? /* @__PURE__ */ new Map();
|
|
105
|
+
const after = require_permissionless_api_oracles_score_formula.finalPrice(under.get(main) ?? null, reserve ? under.get(reserve) ?? null : void 0);
|
|
106
|
+
const factor = require_permissionless_api_oracles_score_formula.factorOf(before, after);
|
|
107
|
+
const members = [...onMain.get(group) ?? [], ...onReserve.get(group) ?? []];
|
|
108
|
+
return {
|
|
109
|
+
group,
|
|
110
|
+
identified: members.every((source) => source.identified),
|
|
111
|
+
feeds: [...new Set(members.map((source) => source.description))],
|
|
112
|
+
reaches: onMain.has(group) ? onReserve.has(group) ? "both" : "main" : "reserve",
|
|
113
|
+
factor,
|
|
114
|
+
score: require_permissionless_api_oracles_score_formula.scoreOfFactor(factor)
|
|
115
|
+
};
|
|
116
|
+
});
|
|
119
117
|
return {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
ratioFactor,
|
|
126
|
-
score: require_permissionless_api_oracles_score_formula.scoreOfFactor(factor)
|
|
118
|
+
hasReserve: reserve !== void 0,
|
|
119
|
+
price: Number((0, viem.formatUnits)(before, 8)),
|
|
120
|
+
unverified: reserve !== void 0 && impacts.some((impact) => !impact.identified && impact.reaches !== "both"),
|
|
121
|
+
notes,
|
|
122
|
+
...require_permissionless_api_oracles_score_formula.worstOf(impacts)
|
|
127
123
|
};
|
|
124
|
+
}),
|
|
125
|
+
sourcesOf
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Every asset of one chain, scored by what each of its source groups can do
|
|
130
|
+
* to the price its running markets hold for it.
|
|
131
|
+
*
|
|
132
|
+
* One read of the oracles' feed trees, then the simulation of every market's
|
|
133
|
+
* main and reserve pair at once — see {@link simulatePairs}.
|
|
134
|
+
**/
|
|
135
|
+
async function runAssetScores({ client, chainId, slots, compressor, logger }) {
|
|
136
|
+
const running = slots.filter((slot) => !slot.isShutdown);
|
|
137
|
+
const oracles = [...new Set(running.map((slot) => lower(slot.priceOracle)))];
|
|
138
|
+
const { nodes, answers, unreadableOracles } = await require_permissionless_api_oracles_stress_run.readForest(client, compressor, oracles);
|
|
139
|
+
for (const oracle of unreadableOracles) logger?.warn(`price oracle ${oracle} of chain ${chainId} would not report its feed tree; the markets on it are not scored`);
|
|
140
|
+
const slotsByPosition = /* @__PURE__ */ new Map();
|
|
141
|
+
for (const slot of running) {
|
|
142
|
+
const key = `${lower(slot.market)}:${lower(slot.token)}`;
|
|
143
|
+
const entry = slotsByPosition.get(key) ?? {};
|
|
144
|
+
entry[slot.reserve ? "reserve" : "main"] = slot;
|
|
145
|
+
slotsByPosition.set(key, entry);
|
|
146
|
+
}
|
|
147
|
+
const positions = [];
|
|
148
|
+
for (const { main, reserve } of slotsByPosition.values()) {
|
|
149
|
+
if (!main) continue;
|
|
150
|
+
const mainNode = nodes.get(lower(main.priceFeed));
|
|
151
|
+
if (!mainNode || mainNode.contractType === ZERO_TYPE) continue;
|
|
152
|
+
positions.push({
|
|
153
|
+
main,
|
|
154
|
+
reserve
|
|
128
155
|
});
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
})
|
|
136
|
-
|
|
156
|
+
}
|
|
157
|
+
const { results, sourcesOf } = await simulatePairs({
|
|
158
|
+
client,
|
|
159
|
+
chainId,
|
|
160
|
+
nodes,
|
|
161
|
+
answers,
|
|
162
|
+
pairs: positions.map(({ main, reserve }) => pairOf(lower(main.priceFeed), reserve ? lower(reserve.priceFeed) : void 0, nodes)),
|
|
163
|
+
logger
|
|
164
|
+
});
|
|
165
|
+
const side = (slot) => ({
|
|
166
|
+
feed: lower(slot.priceFeed),
|
|
167
|
+
name: slot.priceFeedName,
|
|
168
|
+
contractType: nodes.get(lower(slot.priceFeed))?.contractType ?? "",
|
|
169
|
+
sources: sourcesOf(lower(slot.priceFeed))
|
|
170
|
+
});
|
|
171
|
+
const byAsset = /* @__PURE__ */ new Map();
|
|
172
|
+
positions.forEach(({ main, reserve }, i) => {
|
|
173
|
+
const result = results[i];
|
|
174
|
+
if (!result) {
|
|
175
|
+
logger?.warn(`feed ${lower(main.priceFeed)} of chain ${chainId} does not answer; ${main.tokenSymbol ?? main.token} in ${main.marketName ?? main.market} is not scored`);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
const asset = lower(main.token);
|
|
137
179
|
const entry = byAsset.get(asset) ?? {
|
|
138
180
|
symbol: main.tokenSymbol,
|
|
139
181
|
markets: []
|
|
@@ -146,15 +188,11 @@ async function runAssetScores({ client, chainId, slots, compressor, logger }) {
|
|
|
146
188
|
curatorName: main.curatorName,
|
|
147
189
|
main: side(main),
|
|
148
190
|
reserve: reserve ? side(reserve) : null,
|
|
149
|
-
|
|
150
|
-
price: Number((0, viem.formatUnits)(before, 8)),
|
|
151
|
-
unverified,
|
|
152
|
-
notes,
|
|
153
|
-
...require_permissionless_api_oracles_score_formula.worstOf(impacts)
|
|
191
|
+
...result
|
|
154
192
|
});
|
|
155
|
-
}
|
|
193
|
+
});
|
|
156
194
|
return [...byAsset].map(([asset, { symbol, markets }]) => {
|
|
157
|
-
const sorted = markets.sort((a, b) =>
|
|
195
|
+
const sorted = markets.sort((a, b) => b.score - a.score);
|
|
158
196
|
const worst = sorted[0] ?? null;
|
|
159
197
|
return {
|
|
160
198
|
chainId,
|
|
@@ -167,5 +205,48 @@ async function runAssetScores({ client, chainId, slots, compressor, logger }) {
|
|
|
167
205
|
};
|
|
168
206
|
});
|
|
169
207
|
}
|
|
208
|
+
/**
|
|
209
|
+
* Any main feed and reserve on a chain, scored the way a market holding them
|
|
210
|
+
* would be — whether or not any market does yet.
|
|
211
|
+
*
|
|
212
|
+
* The two trees are read off the compressor by feed rather than by oracle,
|
|
213
|
+
* so a feed the curator has only just deployed scores as well as one in use.
|
|
214
|
+
*
|
|
215
|
+
* @throws Where there is nothing to score: the compressor cannot read a
|
|
216
|
+
* feed, the main feed is a `ZeroPriceFeed`, or the final price does not
|
|
217
|
+
* answer even with every pull feed under it freshened.
|
|
218
|
+
**/
|
|
219
|
+
async function runFeedPairScore({ client, chainId, compressor, main, reserve, logger }) {
|
|
220
|
+
const mainFeed = lower(main);
|
|
221
|
+
const reserveFeed = reserve ? lower(reserve) : void 0;
|
|
222
|
+
const feeds = reserveFeed && reserveFeed !== mainFeed ? [mainFeed, reserveFeed] : [mainFeed];
|
|
223
|
+
const { nodes, answers } = await require_permissionless_api_oracles_stress_run.readFeedTrees(client, compressor, feeds).catch((cause) => {
|
|
224
|
+
throw new Error(`the PriceFeedCompressor of chain ${chainId} would not read ${feeds.join(" and ")}: not a price feed it knows`, { cause });
|
|
225
|
+
});
|
|
226
|
+
for (const feed of [mainFeed, reserveFeed]) if (feed && !nodes.has(feed)) throw new Error(`feed ${feed} of chain ${chainId} is not one the PriceFeedCompressor can read`);
|
|
227
|
+
if (nodes.get(mainFeed)?.contractType === ZERO_TYPE) throw new Error(`the main feed ${mainFeed} is a ZeroPriceFeed: it prices nothing, so nothing can move`);
|
|
228
|
+
const { results, sourcesOf } = await simulatePairs({
|
|
229
|
+
client,
|
|
230
|
+
chainId,
|
|
231
|
+
nodes,
|
|
232
|
+
answers,
|
|
233
|
+
pairs: [pairOf(mainFeed, reserveFeed, nodes)],
|
|
234
|
+
logger
|
|
235
|
+
});
|
|
236
|
+
const result = results[0];
|
|
237
|
+
if (!result) throw new Error(`the main feed ${mainFeed} of chain ${chainId} does not answer, so there is no price to move`);
|
|
238
|
+
const side = (feed) => ({
|
|
239
|
+
feed,
|
|
240
|
+
name: null,
|
|
241
|
+
contractType: nodes.get(feed)?.contractType ?? "",
|
|
242
|
+
sources: sourcesOf(feed)
|
|
243
|
+
});
|
|
244
|
+
return {
|
|
245
|
+
main: side(mainFeed),
|
|
246
|
+
reserve: reserveFeed ? side(reserveFeed) : null,
|
|
247
|
+
...result
|
|
248
|
+
};
|
|
249
|
+
}
|
|
170
250
|
//#endregion
|
|
171
251
|
exports.runAssetScores = runAssetScores;
|
|
252
|
+
exports.runFeedPairScore = runFeedPairScore;
|
|
@@ -12,6 +12,7 @@ exports.classifySources = require_permissionless_api_oracles_stress_classify.cla
|
|
|
12
12
|
exports.groupOverride = require_permissionless_api_oracles_stress_override.groupOverride;
|
|
13
13
|
exports.perturb = require_permissionless_api_oracles_stress_override.perturb;
|
|
14
14
|
exports.rateStub = require_permissionless_api_oracles_stress_override.rateStub;
|
|
15
|
+
exports.readFeedTrees = require_permissionless_api_oracles_stress_run.readFeedTrees;
|
|
15
16
|
exports.readForest = require_permissionless_api_oracles_stress_run.readForest;
|
|
16
17
|
exports.readRateTargets = require_permissionless_api_oracles_stress_run.readRateTargets;
|
|
17
18
|
exports.readUnder = require_permissionless_api_oracles_stress_run.readUnder;
|