@gearbox-protocol/sdk 18.2.0-next.11 → 18.2.0-next.12

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 (49) hide show
  1. package/dist/cjs/dev/AccountOpener.js +1 -1
  2. package/dist/cjs/dev/withdrawalUtils.js +1 -1
  3. package/dist/cjs/history/trace/extractTransfers.js +1 -1
  4. package/dist/cjs/onchain/accounts/CreditAccountsServiceV310.js +2 -2
  5. package/dist/cjs/onchain/accounts/liquidations/LiquidationsService.js +1 -1
  6. package/dist/cjs/onchain/accounts/withdrawal-compressor/RedemptionLoggerV310Contract.js +2 -1
  7. package/dist/cjs/onchain/accounts/withdrawal-compressor/WithdrawalCompressorV310Contract.js +2 -1
  8. package/dist/cjs/onchain/accounts/withdrawal-compressor/WithdrawalCompressorV311Contract.js +2 -1
  9. package/dist/cjs/onchain/accounts/withdrawal-compressor/WithdrawalCompressorV313Contract.js +1 -1
  10. package/dist/cjs/onchain/base/TokensMeta.js +3 -3
  11. package/dist/cjs/onchain/core/createAddressProvider.js +1 -1
  12. package/dist/cjs/onchain/market/adapters/contracts/AccountMigratorAdapterContract.js +1 -1
  13. package/dist/cjs/onchain/market/adapters/contracts/ERC4626AdapterContract.js +1 -1
  14. package/dist/cjs/onchain/market/credit/CreditFacadeV310BaseContract.js +1 -1
  15. package/dist/cjs/onchain/market/pool/PoolV310Contract.js +1 -1
  16. package/dist/cjs/onchain/market/rwa/createDegenNFT.js +1 -1
  17. package/dist/cjs/onchain/market/zapper/IETHZapperContract.js +2 -1
  18. package/dist/cjs/onchain/market/zapper/ZapperContract.js +1 -1
  19. package/dist/cjs/onchain/preview/preview/previewPoolPositionOperation.js +2 -2
  20. package/dist/cjs/onchain/utils/viem/simulateWithPriceUpdates.js +1 -1
  21. package/dist/cjs/permissionless/api/index.js +7 -3
  22. package/dist/cjs/permissionless/api/oracles/PermissionlessOracles.js +11 -11
  23. package/dist/cjs/permissionless/api/oracles/index.js +7 -3
  24. package/dist/cjs/permissionless/api/oracles/score/formula.js +44 -82
  25. package/dist/cjs/permissionless/api/oracles/score/index.js +6 -3
  26. package/dist/cjs/permissionless/api/oracles/score/run.js +129 -46
  27. package/dist/cjs/permissionless/api/oracles/stress/index.js +1 -0
  28. package/dist/cjs/permissionless/api/oracles/stress/run.js +5 -2
  29. package/dist/cjs/permissionless/index.js +7 -3
  30. package/dist/esm/permissionless/api/index.js +3 -3
  31. package/dist/esm/permissionless/api/oracles/PermissionlessOracles.js +11 -11
  32. package/dist/esm/permissionless/api/oracles/index.js +3 -3
  33. package/dist/esm/permissionless/api/oracles/score/formula.js +39 -80
  34. package/dist/esm/permissionless/api/oracles/score/index.js +2 -2
  35. package/dist/esm/permissionless/api/oracles/score/run.js +131 -48
  36. package/dist/esm/permissionless/api/oracles/stress/index.js +2 -2
  37. package/dist/esm/permissionless/api/oracles/stress/run.js +5 -3
  38. package/dist/esm/permissionless/index.js +3 -3
  39. package/dist/types/permissionless/api/index.d.ts +4 -4
  40. package/dist/types/permissionless/api/oracles/PermissionlessOracles.d.ts +11 -11
  41. package/dist/types/permissionless/api/oracles/index.d.ts +4 -4
  42. package/dist/types/permissionless/api/oracles/score/formula.d.ts +31 -38
  43. package/dist/types/permissionless/api/oracles/score/index.d.ts +3 -3
  44. package/dist/types/permissionless/api/oracles/score/run.d.ts +7 -4
  45. package/dist/types/permissionless/api/oracles/score/types.d.ts +50 -22
  46. package/dist/types/permissionless/api/oracles/stress/index.d.ts +2 -2
  47. package/dist/types/permissionless/api/oracles/stress/run.d.ts +17 -1
  48. package/dist/types/permissionless/index.d.ts +4 -4
  49. package/package.json +1 -1
@@ -2,73 +2,156 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_permissionless_api_oracles_stress_classify = require("../stress/classify.js");
3
3
  const require_permissionless_api_oracles_stress_run = require("../stress/run.js");
4
4
  const require_permissionless_api_oracles_score_formula = require("./formula.js");
5
+ let viem = require("viem");
5
6
  //#region src/permissionless/api/oracles/score/run.ts
7
+ const ZERO_TYPE = "PRICE_FEED::ZERO";
8
+ /** Groups simulated at once: one `eth_call` each, all against one node. */
9
+ const CONCURRENCY = 6;
10
+ const feedOf = (slot) => slot.priceFeed.toLowerCase();
6
11
  /**
7
- * How a source is told apart from another when two feeds are compared.
12
+ * Every asset of one chain, scored by what each of its source groups can do
13
+ * to the price its running markets hold for it.
8
14
  *
9
- * A named source by its group, since two Chainlink aggregators are one
10
- * provider failing together. An unnamed one by its contract, since nothing
11
- * says what else it fails with — so the same contract on both sides is
12
- * shared, and two different ones are not.
13
- */
14
- function toScoreSource(source) {
15
- return {
16
- key: source.identified ? `group:${source.group}` : `contract:${source.target}`,
17
- name: source.group,
18
- identified: source.identified
19
- };
20
- }
21
- /**
22
- * Every asset of one chain, scored by how its running markets price it.
23
- *
24
- * One read of every oracle's feed tree and one of the LP feeds' rate
25
- * contracts, both multicalls: the score is a reading of the trees and needs
26
- * no simulation.
15
+ * One read of the oracles' feed trees, one of the LP feeds' rate contracts,
16
+ * one of every market feed as it stands — then one `eth_call` per source
17
+ * group, with the group's sources overridden to answer
18
+ * {@link SCORE_MULTIPLIER} times what they answer now and every market feed
19
+ * read under it.
27
20
  **/
28
21
  async function runAssetScores({ client, chainId, slots, compressor, logger }) {
29
22
  const running = slots.filter((slot) => !slot.isShutdown);
30
23
  const oracles = [...new Set(running.map((slot) => slot.priceOracle.toLowerCase()))];
31
- const { nodes, unreadableOracles } = await require_permissionless_api_oracles_stress_run.readForest(client, compressor, oracles);
24
+ const { nodes, answers, unreadableOracles } = await require_permissionless_api_oracles_stress_run.readForest(client, compressor, oracles);
32
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`);
33
- const { targets } = 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));
34
- const { sources } = require_permissionless_api_oracles_stress_classify.classifySources(chainId, [...nodes.values()], targets, () => null);
35
- const byTarget = new Map(sources.filter((source) => source.perturbable).map((source) => [source.target, toScoreSource(source)]));
36
- const feedOf = (slot) => slot.priceFeed.toLowerCase();
37
- const reach = require_permissionless_api_oracles_stress_classify.sourcesByFeed([...new Set(running.map(feedOf))], nodes, sources);
38
- const sourcesOf = (feed) => [...reach.get(feed) ?? []].map((target) => byTarget.get(target)).filter((source) => source !== void 0);
39
- const positions = /* @__PURE__ */ new Map();
26
+ 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
+ const { sources, unresolved } = require_permissionless_api_oracles_stress_classify.classifySources(chainId, [...nodes.values()], targets, (target) => rateBaselines.get(target) ?? answers.get(target) ?? null);
28
+ 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 slotsByPosition = /* @__PURE__ */ new Map();
40
30
  for (const slot of running) {
41
31
  const key = `${slot.market.toLowerCase()}:${slot.token.toLowerCase()}`;
42
- const position = positions.get(key) ?? {};
43
- position[slot.reserve ? "reserve" : "main"] = slot;
44
- positions.set(key, position);
32
+ const entry = slotsByPosition.get(key) ?? {};
33
+ entry[slot.reserve ? "reserve" : "main"] = slot;
34
+ slotsByPosition.set(key, entry);
45
35
  }
46
- const byAsset = /* @__PURE__ */ new Map();
47
- for (const { main, reserve } of positions.values()) {
36
+ const positions = [];
37
+ for (const { main, reserve } of slotsByPosition.values()) {
48
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");
48
+ }
49
+ positions.push({
50
+ main,
51
+ reserve,
52
+ reserveFeed,
53
+ notes
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)])];
60
+ const updatedAt = BigInt(Math.floor(Date.now() / 1e3));
61
+ const drivable = (source) => source !== void 0 && source.perturbable && source.baseline !== null;
62
+ const fresh = sources.filter((source) => drivable(source) && source.kind === "feed");
63
+ const asIs = await require_permissionless_api_oracles_stress_run.readUnder(client, roots, [], 1, updatedAt);
64
+ const stale = roots.filter((root) => (asIs.get(root) ?? null) === null);
65
+ const freshened = stale.length > 0 ? await require_permissionless_api_oracles_stress_run.readUnder(client, stale, [], 1, updatedAt, fresh) : /* @__PURE__ */ new Map();
66
+ const base = new Map(roots.map((root) => [root, asIs.get(root) ?? freshened.get(root) ?? null]));
67
+ const asIsRoots = roots.filter((root) => (asIs.get(root) ?? null) !== null);
68
+ const freshRoots = stale.filter((root) => (freshened.get(root) ?? null) !== null);
69
+ /** Every root under a lie, each read the way its base was. */
70
+ const readLying = async (members) => {
71
+ const [plain, refreshed] = await Promise.all([asIsRoots.length > 0 ? require_permissionless_api_oracles_stress_run.readUnder(client, asIsRoots, members, 5, updatedAt) : /* @__PURE__ */ new Map(), freshRoots.length > 0 ? require_permissionless_api_oracles_stress_run.readUnder(client, freshRoots, members, 5, updatedAt, fresh) : /* @__PURE__ */ new Map()]);
72
+ return new Map([...plain, ...refreshed]);
73
+ };
74
+ for (const position of positions) if (position.reserveFeed && (base.get(position.reserveFeed) ?? null) === null) {
75
+ position.reserveFeed = void 0;
76
+ position.notes.push("reserve does not answer");
77
+ }
78
+ const reach = require_permissionless_api_oracles_stress_classify.sourcesByFeed(roots, nodes, sources);
79
+ const sourceByTarget = new Map(sources.map((source) => [source.target, source]));
80
+ const byGroup = /* @__PURE__ */ new Map();
81
+ for (const source of sources.filter(drivable)) byGroup.set(source.group, [...byGroup.get(source.group) ?? [], source]);
82
+ const reachedGroups = /* @__PURE__ */ new Set();
83
+ for (const root of priced) for (const target of reach.get(root) ?? []) {
84
+ const source = sourceByTarget.get(target);
85
+ if (drivable(source)) reachedGroups.add(source.group);
86
+ }
87
+ const stressed = /* @__PURE__ */ new Map();
88
+ const queue = [...reachedGroups];
89
+ for (let i = 0; i < queue.length; i += CONCURRENCY) await Promise.all(queue.slice(i, i + CONCURRENCY).map(async (group) => {
90
+ stressed.set(group, await readLying(byGroup.get(group) ?? []));
91
+ }));
92
+ const groupsOf = (feed) => {
93
+ const out = /* @__PURE__ */ new Map();
94
+ for (const target of feed ? reach.get(feed) ?? [] : []) {
95
+ const source = sourceByTarget.get(target);
96
+ if (drivable(source)) out.set(source.group, [...out.get(source.group) ?? [], source]);
97
+ }
98
+ return out;
99
+ };
100
+ const byAsset = /* @__PURE__ */ new Map();
101
+ for (const { main, reserve, reserveFeed, notes } of positions) {
102
+ const mainFeed = feedOf(main);
103
+ const before = require_permissionless_api_oracles_score_formula.finalPrice(base.get(mainFeed) ?? null, reserveFeed ? base.get(reserveFeed) ?? null : void 0);
104
+ if (before === null || before <= 0n) {
105
+ logger?.warn(`feed ${mainFeed} of chain ${chainId} does not answer; ${main.tokenSymbol ?? main.token} in ${main.marketName ?? main.market} is not scored`);
106
+ continue;
107
+ }
108
+ const onMain = groupsOf(mainFeed);
109
+ const onReserve = groupsOf(reserveFeed);
110
+ const impacts = [.../* @__PURE__ */ new Set([...onMain.keys(), ...onReserve.keys()])].map((group) => {
111
+ const under = stressed.get(group) ?? /* @__PURE__ */ new Map();
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) ?? []];
119
+ return {
120
+ group,
121
+ identified: members.every((source) => source.identified),
122
+ feeds: [...new Set(members.map((source) => source.description))],
123
+ reaches: onMain.has(group) ? onReserve.has(group) ? "both" : "main" : "reserve",
124
+ factor,
125
+ ratioFactor,
126
+ score: require_permissionless_api_oracles_score_formula.scoreOfFactor(factor)
127
+ };
128
+ });
129
+ const unverified = reserveFeed !== void 0 && impacts.some((impact) => !impact.identified && impact.reaches !== "both");
130
+ const side = (slot) => ({
131
+ feed: feedOf(slot),
132
+ name: slot.priceFeedName,
133
+ contractType: nodes.get(feedOf(slot))?.contractType ?? "",
134
+ sources: [...groupsOf(feedOf(slot)).keys()]
135
+ });
49
136
  const asset = main.token.toLowerCase();
50
137
  const entry = byAsset.get(asset) ?? {
51
138
  symbol: main.tokenSymbol,
52
139
  markets: []
53
140
  };
54
141
  byAsset.set(asset, entry);
55
- const mainNode = nodes.get(feedOf(main));
56
- if (!mainNode) continue;
57
- if (mainNode.contractType === "PRICE_FEED::ZERO") continue;
58
- const side = (slot) => ({
59
- feed: {
60
- feed: feedOf(slot),
61
- name: slot.priceFeedName,
62
- contractType: nodes.get(feedOf(slot))?.contractType ?? ""
63
- },
64
- sources: sourcesOf(feedOf(slot))
65
- });
66
- entry.markets.push(require_permissionless_api_oracles_score_formula.scoreMarket({
142
+ entry.markets.push({
67
143
  market: main.market,
68
144
  marketName: main.marketName,
69
145
  marketConfigurator: main.marketConfigurator,
70
- curatorName: main.curatorName
71
- }, side(main), reserve ? side(reserve) : null));
146
+ curatorName: main.curatorName,
147
+ main: side(main),
148
+ reserve: reserve ? side(reserve) : null,
149
+ hasReserve: reserveFeed !== void 0,
150
+ price: Number((0, viem.formatUnits)(before, 8)),
151
+ unverified,
152
+ notes,
153
+ ...require_permissionless_api_oracles_score_formula.worstOf(impacts)
154
+ });
72
155
  }
73
156
  return [...byAsset].map(([asset, { symbol, markets }]) => {
74
157
  const sorted = markets.sort((a, b) => a.score - b.score);
@@ -14,6 +14,7 @@ exports.perturb = require_permissionless_api_oracles_stress_override.perturb;
14
14
  exports.rateStub = require_permissionless_api_oracles_stress_override.rateStub;
15
15
  exports.readForest = require_permissionless_api_oracles_stress_run.readForest;
16
16
  exports.readRateTargets = require_permissionless_api_oracles_stress_run.readRateTargets;
17
+ exports.readUnder = require_permissionless_api_oracles_stress_run.readUnder;
17
18
  exports.roundDataStub = require_permissionless_api_oracles_stress_override.roundDataStub;
18
19
  exports.runStressTest = require_permissionless_api_oracles_stress_run.runStressTest;
19
20
  exports.scoreGroup = require_permissionless_api_oracles_stress_score.scoreGroup;
@@ -161,8 +161,10 @@ async function readRateTargets(client, feeds) {
161
161
  * scenario rather than a round trip per feed. A feed that reverts comes back
162
162
  * `null`, which is an outcome and not an error.
163
163
  */
164
- async function readUnder(client, feeds, sources, multiplier, updatedAt) {
165
- const overrides = require_permissionless_api_oracles_stress_override.groupOverride(sources, multiplier, updatedAt);
164
+ async function readUnder(client, feeds, sources, multiplier, updatedAt, freshen = []) {
165
+ const driven = require_permissionless_api_oracles_stress_override.groupOverride(sources, multiplier, updatedAt);
166
+ const taken = new Set(driven.map((override) => override.address.toLowerCase()));
167
+ const overrides = [...require_permissionless_api_oracles_stress_override.groupOverride(freshen, 1, updatedAt).filter((override) => !taken.has(override.address.toLowerCase())), ...driven];
166
168
  const results = await client.multicall({
167
169
  contracts: feeds.map((address) => ({
168
170
  address,
@@ -429,4 +431,5 @@ exports.DEFAULT_SCENARIOS = DEFAULT_SCENARIOS;
429
431
  exports.RATE_FEED_TYPE = RATE_FEED_TYPE;
430
432
  exports.readForest = readForest;
431
433
  exports.readRateTargets = readRateTargets;
434
+ exports.readUnder = readUnder;
432
435
  exports.runStressTest = runStressTest;
@@ -52,7 +52,6 @@ exports.AccessMode = require_permissionless_bindings_types.AccessMode;
52
52
  exports.AddressProviderContract = require_permissionless_bindings_address_provider.AddressProviderContract;
53
53
  exports.Addresses = require_permissionless_deployment_addresses.Addresses;
54
54
  exports.ArchiveTransport = require_permissionless_chains_archive_transport.ArchiveTransport;
55
- exports.BOUND_WEIGHT = require_permissionless_api_oracles_score_formula.BOUND_WEIGHT;
56
55
  exports.BYTECODE_REPOSITORY = require_permissionless_utils_literals.BYTECODE_REPOSITORY;
57
56
  exports.BytecodeRepositoryContract = require_permissionless_bindings_bytecode_repository.BytecodeRepositoryContract;
58
57
  exports.CROSS_CHAIN_GOVERNANCE = require_permissionless_utils_literals.CROSS_CHAIN_GOVERNANCE;
@@ -65,7 +64,6 @@ exports.GET_PRICES_CHUNK_SIZE = require_permissionless_api_oracles_Permissionles
65
64
  exports.GET_PRICES_GAS_LIMIT_BY_CHAIN_ID = require_permissionless_api_oracles_PermissionlessOracles.GET_PRICES_GAS_LIMIT_BY_CHAIN_ID;
66
65
  exports.GearboxPermissionless = require_permissionless_api_GearboxPermissionless.GearboxPermissionless;
67
66
  exports.GovernorContract = require_permissionless_bindings_governance_governor.GovernorContract;
68
- exports.INDEPENDENCE_WEIGHT = require_permissionless_api_oracles_score_formula.INDEPENDENCE_WEIGHT;
69
67
  exports.INSTANCE_MANAGER = require_permissionless_utils_literals.INSTANCE_MANAGER;
70
68
  exports.InstanceManagerContract = require_permissionless_bindings_instance_manager.InstanceManagerContract;
71
69
  exports.InstanceOwnerTransactions = require_permissionless_api_transactions_InstanceOwnerTransactions.InstanceOwnerTransactions;
@@ -88,7 +86,9 @@ exports.PriceFeedSaltMissingError = require_permissionless_api_errors.PriceFeedS
88
86
  exports.PriceFeedStalenessUnknownError = require_permissionless_api_errors.PriceFeedStalenessUnknownError;
89
87
  exports.PriceFeedStoreContract = require_permissionless_bindings_price_feed_store.PriceFeedStoreContract;
90
88
  exports.RATE_FEED_TYPE = require_permissionless_api_oracles_stress_run.RATE_FEED_TYPE;
89
+ exports.REVERT_SCORE = require_permissionless_api_oracles_score_formula.REVERT_SCORE;
91
90
  exports.RoutingManagerContract = require_permissionless_bindings_router_routing_manager.RoutingManagerContract;
91
+ exports.SCORE_MULTIPLIER = require_permissionless_api_oracles_score_formula.SCORE_MULTIPLIER;
92
92
  exports.TOKEN_COMPRESSOR = require_permissionless_utils_literals.TOKEN_COMPRESSOR;
93
93
  exports.TimeLockContract = require_permissionless_bindings_governance_timelock.TimeLockContract;
94
94
  exports.TokenCompressorContract = require_permissionless_bindings_compressors_token_compressor.TokenCompressorContract;
@@ -110,6 +110,8 @@ exports.deepJsonParse = require_permissionless_utils_format.deepJsonParse;
110
110
  exports.deployUsingPublicCreate2 = require_permissionless_utils_create2.deployUsingPublicCreate2;
111
111
  exports.deriveEffectiveStalenessPeriod = require_permissionless_api_deploy_effective_staleness.deriveEffectiveStalenessPeriod;
112
112
  exports.encodeFunctionParams = require_permissionless_utils_abi_encoder.encodeFunctionParams;
113
+ exports.factorOf = require_permissionless_api_oracles_score_formula.factorOf;
114
+ exports.finalPrice = require_permissionless_api_oracles_score_formula.finalPrice;
113
115
  exports.formatBytecodeSize = require_permissionless_utils_format.formatBytecodeSize;
114
116
  exports.formatBytecodeVersion = require_permissionless_utils_format.formatBytecodeVersion;
115
117
  exports.getBlockNumberByTimestamp = require_permissionless_utils_block_utils.getBlockNumberByTimestamp;
@@ -131,13 +133,15 @@ exports.priceFeedSetupParams = require_permissionless_bindings_pricefeeds_index.
131
133
  exports.rateStub = require_permissionless_api_oracles_stress_override.rateStub;
132
134
  exports.readForest = require_permissionless_api_oracles_stress_run.readForest;
133
135
  exports.readRateTargets = require_permissionless_api_oracles_stress_run.readRateTargets;
136
+ exports.readUnder = require_permissionless_api_oracles_stress_run.readUnder;
134
137
  exports.roundDataStub = require_permissionless_api_oracles_stress_override.roundDataStub;
135
138
  exports.runAssetScores = require_permissionless_api_oracles_score_run.runAssetScores;
136
139
  exports.runStressTest = require_permissionless_api_oracles_stress_run.runStressTest;
137
140
  exports.scoreGroup = require_permissionless_api_oracles_stress_score.scoreGroup;
138
- exports.scoreMarket = require_permissionless_api_oracles_score_formula.scoreMarket;
141
+ exports.scoreOfFactor = require_permissionless_api_oracles_score_formula.scoreOfFactor;
139
142
  exports.shortenHash = require_permissionless_utils_format.shortenHash;
140
143
  exports.significantTrunc = require_permissionless_utils_format.significantTrunc;
141
144
  exports.slotHarm = require_permissionless_api_oracles_stress_score.slotHarm;
142
145
  exports.sourcesByFeed = require_permissionless_api_oracles_stress_classify.sourcesByFeed;
143
146
  exports.verdictOf = require_permissionless_api_oracles_stress_score.verdictOf;
147
+ exports.worstOf = require_permissionless_api_oracles_score_formula.worstOf;
@@ -9,8 +9,8 @@ import "./deploy/index.js";
9
9
  import { classifySources, sourcesByFeed } from "./oracles/stress/classify.js";
10
10
  import { UNBOUNDED_DOWN, UNBOUNDED_UP, groupOverride, perturb, rateStub, roundDataStub } from "./oracles/stress/override.js";
11
11
  import { scoreGroup, slotHarm, verdictOf } from "./oracles/stress/score.js";
12
- import { DEFAULT_SCENARIOS, RATE_FEED_TYPE, readForest, readRateTargets, runStressTest } from "./oracles/stress/run.js";
13
- import { BOUND_WEIGHT, INDEPENDENCE_WEIGHT, scoreMarket } from "./oracles/score/formula.js";
12
+ import { DEFAULT_SCENARIOS, RATE_FEED_TYPE, readForest, readRateTargets, readUnder, runStressTest } from "./oracles/stress/run.js";
13
+ import { REVERT_SCORE, SCORE_MULTIPLIER, factorOf, finalPrice, scoreOfFactor, worstOf } from "./oracles/score/formula.js";
14
14
  import { runAssetScores } from "./oracles/score/run.js";
15
15
  import { GET_PRICES_CHUNK_SIZE, GET_PRICES_GAS_LIMIT_BY_CHAIN_ID, PermissionlessOracles } from "./oracles/PermissionlessOracles.js";
16
16
  import { InstanceOwnerTransactions } from "./transactions/InstanceOwnerTransactions.js";
@@ -18,4 +18,4 @@ import { GearboxPermissionless } from "./GearboxPermissionless.js";
18
18
  import "./oracles/index.js";
19
19
  import "./transactions/index.js";
20
20
  import "./types.js";
21
- export { BOUND_WEIGHT, ChainsNamespace, DEFAULT_DEPLOY_SALT, DEFAULT_SCENARIOS, GET_PRICES_CHUNK_SIZE, GET_PRICES_GAS_LIMIT_BY_CHAIN_ID, GearboxPermissionless, INDEPENDENCE_WEIGHT, InstanceOwnerTransactions, PermissionlessAnalytics, PermissionlessChainUnreachableError, PermissionlessDeploy, PermissionlessNotAttachedError, PermissionlessOracles, PriceFeedNotInStoreError, PriceFeedSaltMissingError, PriceFeedStalenessUnknownError, RATE_FEED_TYPE, UNBOUNDED_DOWN, UNBOUNDED_UP, classifySources, deriveEffectiveStalenessPeriod, groupOverride, perturb, priceFeedSalt, rateStub, readForest, readRateTargets, roundDataStub, runAssetScores, runStressTest, scoreGroup, scoreMarket, slotHarm, sourcesByFeed, verdictOf };
21
+ export { ChainsNamespace, DEFAULT_DEPLOY_SALT, DEFAULT_SCENARIOS, GET_PRICES_CHUNK_SIZE, GET_PRICES_GAS_LIMIT_BY_CHAIN_ID, GearboxPermissionless, InstanceOwnerTransactions, PermissionlessAnalytics, PermissionlessChainUnreachableError, PermissionlessDeploy, PermissionlessNotAttachedError, PermissionlessOracles, PriceFeedNotInStoreError, PriceFeedSaltMissingError, PriceFeedStalenessUnknownError, RATE_FEED_TYPE, REVERT_SCORE, SCORE_MULTIPLIER, UNBOUNDED_DOWN, UNBOUNDED_UP, classifySources, deriveEffectiveStalenessPeriod, factorOf, finalPrice, groupOverride, perturb, priceFeedSalt, rateStub, readForest, readRateTargets, readUnder, roundDataStub, runAssetScores, runStressTest, scoreGroup, scoreOfFactor, slotHarm, sourcesByFeed, verdictOf, worstOf };
@@ -361,17 +361,17 @@ var PermissionlessOracles = class extends AbstractOffchainNamespace {
361
361
  /**
362
362
  * Every asset's score out of 10 for how its markets guard its price.
363
363
  *
364
- * A market scores 10 when its reserve feed is a second feed on other
365
- * sources than the main one, and one of the two is bounded — a
366
- * `BoundedPriceFeed` or a Gearbox LP feed. It loses 6 points as the two
367
- * feeds' sources overlap — all of them if there is no reserve, it is a
368
- * `ZeroPriceFeed`, or it is the main feed again — and 5 if neither is
369
- * bounded. An asset scores its worst running market that takes it as
370
- * collateral.
371
- *
372
- * Read off the chain, one multicall of feed trees per chain, so the score
373
- * is of the trees as they stand rather than as last indexed. A chain with
374
- * no client configured is left out with a warning rather than failing the
364
+ * Each source group behind an asset's feeds — Chainlink, Redstone, Lido and
365
+ * so on — is made in turn to answer five times what it answers now, and a
366
+ * market scores by how far that moves its final price, `min(main,
367
+ * reserve)`: 10 for not at all, 0 for the full five times, on a log scale.
368
+ * A price that stops answering scores 5. An asset scores its worst running
369
+ * market that takes it as collateral.
370
+ *
371
+ * Simulated on the chain as it stands, through state overrides in
372
+ * `eth_call`: one call per source group per chain, so the node behind the
373
+ * client has to accept overrides. A chain with no client configured, or
374
+ * whose node refuses, is left out with a warning rather than failing the
375
375
  * rest.
376
376
  *
377
377
  * ```ts
@@ -1,11 +1,11 @@
1
1
  import { classifySources, sourcesByFeed } from "./stress/classify.js";
2
2
  import { UNBOUNDED_DOWN, UNBOUNDED_UP, groupOverride, perturb, rateStub, roundDataStub } from "./stress/override.js";
3
3
  import { scoreGroup, slotHarm, verdictOf } from "./stress/score.js";
4
- import { DEFAULT_SCENARIOS, RATE_FEED_TYPE, readForest, readRateTargets, runStressTest } from "./stress/run.js";
5
- import { BOUND_WEIGHT, INDEPENDENCE_WEIGHT, scoreMarket } from "./score/formula.js";
4
+ import { DEFAULT_SCENARIOS, RATE_FEED_TYPE, readForest, readRateTargets, readUnder, runStressTest } from "./stress/run.js";
5
+ import { REVERT_SCORE, SCORE_MULTIPLIER, factorOf, finalPrice, scoreOfFactor, worstOf } from "./score/formula.js";
6
6
  import { runAssetScores } from "./score/run.js";
7
7
  import { GET_PRICES_CHUNK_SIZE, GET_PRICES_GAS_LIMIT_BY_CHAIN_ID, PermissionlessOracles } from "./PermissionlessOracles.js";
8
8
  import "./score/index.js";
9
9
  import "./stress/index.js";
10
10
  import "./types.js";
11
- export { BOUND_WEIGHT, DEFAULT_SCENARIOS, GET_PRICES_CHUNK_SIZE, GET_PRICES_GAS_LIMIT_BY_CHAIN_ID, INDEPENDENCE_WEIGHT, PermissionlessOracles, RATE_FEED_TYPE, UNBOUNDED_DOWN, UNBOUNDED_UP, classifySources, groupOverride, perturb, rateStub, readForest, readRateTargets, roundDataStub, runAssetScores, runStressTest, scoreGroup, scoreMarket, slotHarm, sourcesByFeed, verdictOf };
11
+ export { DEFAULT_SCENARIOS, GET_PRICES_CHUNK_SIZE, GET_PRICES_GAS_LIMIT_BY_CHAIN_ID, PermissionlessOracles, RATE_FEED_TYPE, REVERT_SCORE, SCORE_MULTIPLIER, UNBOUNDED_DOWN, UNBOUNDED_UP, classifySources, factorOf, finalPrice, groupOverride, perturb, rateStub, readForest, readRateTargets, readUnder, roundDataStub, runAssetScores, runStressTest, scoreGroup, scoreOfFactor, slotHarm, sourcesByFeed, verdictOf, worstOf };
@@ -1,96 +1,55 @@
1
1
  //#region src/permissionless/api/oracles/score/formula.ts
2
+ /** What every source of a group is made to answer, as a multiple of now. */
3
+ const SCORE_MULTIPLIER = 5;
2
4
  /**
3
- * How well a market guards an asset's price, from its two feeds alone.
5
+ * The score of a price that stops answering under the lie.
4
6
  *
5
- * Two questions, and the points are lost for each separately:
6
- *
7
- * - Would the reserve disagree with a main feed that has gone wrong? Only if
8
- * it is a second feed on other sources. A missing reserve, a
9
- * `ZeroPriceFeed`, the main feed again, and a different feed on the same
10
- * sources all answer no.
11
- * - Is either feed bounded? A `BoundedPriceFeed` caps the answer from above,
12
- * and a Gearbox LP feed clamps its exchange rate to a window.
13
- *
14
- * Independence costs more than the bound, 6 points against 5, because it is
15
- * the broader protection: a bounded feed caps only from above, and an LP
16
- * feed's window holds only its rate, not the price of what the rate is in.
17
- * Either failure alone lands a market at 4 or 5; both together at 0.
7
+ * Half marks: nobody can borrow against a wrong number, but nobody can be
8
+ * liquidated either until the feed is replaced.
18
9
  */
19
- const INDEPENDENCE_WEIGHT = 6;
20
- const BOUND_WEIGHT = 5;
10
+ const REVERT_SCORE = 5;
21
11
  /**
22
- * The feed types that bound what they answer: the `BoundedPriceFeed`, and
23
- * every Gearbox LP feed, all of which clamp the rate they read.
12
+ * 0 to 10 by how far the final price moved, on a log scale against the lie
13
+ * itself: 10 for a price that did not move, 0 for one that moved by the full
14
+ * {@link SCORE_MULTIPLIER} or more, either way. Rounded to a half point.
15
+ *
16
+ * A bound holding a price within a few percent keeps a 10; a price that let
17
+ * half the lie through, ×√5, scores 5.
24
18
  */
25
- const BOUNDED_TYPE = /::(BOUNDED|ERC4626|CURVE_\w+|WSTETH|YEARN|MELLOW\w*|BALANCER\w*)$/;
26
- const ZERO_TYPE = "PRICE_FEED::ZERO";
19
+ function scoreOfFactor(factor) {
20
+ if (factor === null || !Number.isFinite(factor) || factor <= 0) return 5;
21
+ const moved = Math.abs(Math.log(factor)) / Math.log(5);
22
+ return Math.round(10 * Math.max(0, 1 - moved) * 2) / 2;
23
+ }
27
24
  /**
28
- * The share of the two sides' sources that are not shared, by key.
29
- *
30
- * Nothing on either side — two hardcoded prices — is 0, because a reserve is
31
- * there to be a second opinion and neither of them is one.
25
+ * The price a market settles on: the lower of main and reserve, or the main
26
+ * feed's alone where there is no reserve to compare it with. `null` where a
27
+ * feed it needs does not answer.
32
28
  */
33
- function independenceOf(main, reserve) {
34
- const a = new Set(main.map((source) => source.key));
35
- const b = new Set(reserve.map((source) => source.key));
36
- const union = /* @__PURE__ */ new Set([...a, ...b]);
37
- if (union.size === 0) return 0;
38
- return 1 - [...a].filter((key) => b.has(key)).length / union.size;
29
+ function finalPrice(main, reserve) {
30
+ if (main === null || reserve === null) return null;
31
+ if (reserve === void 0) return main;
32
+ return main < reserve ? main : reserve;
39
33
  }
40
- /** Down to the half point, so that a score is never rounded up into a band. */
41
- function toHalf(value) {
42
- return Math.floor(value * 2 + 1e-9) / 2;
34
+ /** `stressed / base` as a float, `null` where the stressed price is gone. */
35
+ function factorOf(base, stressed) {
36
+ if (stressed === null) return null;
37
+ return Number(stressed) / Number(base);
43
38
  }
44
39
  /**
45
- * One market's score for one asset.
46
- *
47
- * `reserve` is `null` where the market holds no reserve for the asset at all.
48
- * A source is passed per side and keyed by the caller, which is where it is
49
- * decided that an unnamed source is its own contract and a named one is its
50
- * group.
40
+ * The market's score and the group it comes from: the lowest of its groups,
41
+ * ties going to the group that moved the price further. 10 where no source
42
+ * feeds the price at all, since nothing can be made to lie about it.
51
43
  */
52
- function scoreMarket(market, main, reserve) {
53
- const reasons = [];
54
- let hasReserve = true;
55
- if (reserve === null) {
56
- hasReserve = false;
57
- reasons.push("no reserve feed");
58
- } else if (reserve.feed.contractType === ZERO_TYPE) {
59
- hasReserve = false;
60
- reasons.push("reserve is a ZeroPriceFeed");
61
- } else if (reserve.feed.feed.toLowerCase() === main.feed.feed.toLowerCase()) {
62
- hasReserve = false;
63
- reasons.push("reserve is the main feed");
64
- }
65
- const reserveSources = hasReserve && reserve ? reserve.sources : [];
66
- const independence = hasReserve ? independenceOf(main.sources, reserveSources) : 0;
67
- const reserveKeys = new Set(reserveSources.map((source) => source.key));
68
- const mainKeys = new Set(main.sources.map((source) => source.key));
69
- const shared = main.sources.filter((source) => reserveKeys.has(source.key));
70
- const names = (sources) => [...new Set(sources.map((source) => source.name))];
71
- if (hasReserve && independence < 1) reasons.push(shared.length > 0 ? `main and reserve share ${names(shared).join(", ")}` : "neither feed reads a price source");
72
- const unverified = hasReserve && [...main.sources.filter((source) => !reserveKeys.has(source.key)), ...reserveSources.filter((source) => !mainKeys.has(source.key))].some((source) => !source.identified);
73
- const bounded = BOUNDED_TYPE.test(main.feed.contractType) || reserve !== null && BOUNDED_TYPE.test(reserve.feed.contractType);
74
- if (!bounded) reasons.push("neither feed is bounded");
75
- const score = toHalf(Math.max(0, 10 - 6 * (1 - independence) - 5 * (bounded ? 0 : 1)));
44
+ function worstOf(impacts) {
45
+ const distance = (impact) => impact.factor === null ? Infinity : Math.abs(Math.log(impact.factor));
46
+ const sorted = [...impacts].sort((a, b) => a.score - b.score || distance(b) - distance(a));
47
+ const worst = sorted[0] ?? null;
76
48
  return {
77
- ...market,
78
- main: {
79
- ...main.feed,
80
- sources: names(main.sources)
81
- },
82
- reserve: reserve ? {
83
- ...reserve.feed,
84
- sources: names(reserve.sources)
85
- } : null,
86
- hasReserve,
87
- independence,
88
- bounded,
89
- sharedSources: names(shared),
90
- unverified,
91
- score,
92
- reasons
49
+ score: worst?.score ?? 10,
50
+ impacts: sorted,
51
+ worst
93
52
  };
94
53
  }
95
54
  //#endregion
96
- export { BOUND_WEIGHT, INDEPENDENCE_WEIGHT, scoreMarket };
55
+ export { REVERT_SCORE, SCORE_MULTIPLIER, factorOf, finalPrice, scoreOfFactor, worstOf };
@@ -1,4 +1,4 @@
1
- import { BOUND_WEIGHT, INDEPENDENCE_WEIGHT, scoreMarket } from "./formula.js";
1
+ import { REVERT_SCORE, SCORE_MULTIPLIER, factorOf, finalPrice, scoreOfFactor, worstOf } from "./formula.js";
2
2
  import { runAssetScores } from "./run.js";
3
3
  import "./types.js";
4
- export { BOUND_WEIGHT, INDEPENDENCE_WEIGHT, runAssetScores, scoreMarket };
4
+ export { REVERT_SCORE, SCORE_MULTIPLIER, factorOf, finalPrice, runAssetScores, scoreOfFactor, worstOf };