@gearbox-protocol/sdk 18.3.1 → 18.4.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/dist/cjs/abi/310/iKillSwitchProxy.js +130 -0
- package/dist/cjs/onchain/index.js +18 -0
- package/dist/cjs/onchain/market/adapters/abi/adapters/iTreehouseFastlaneAdapter.js +113 -0
- package/dist/cjs/onchain/market/adapters/abi/adapters/iTreehouseRedemptionGatewayAdapter.js +258 -0
- package/dist/cjs/onchain/market/adapters/abi/adapters/iTreehouseRouterAdapter.js +161 -0
- package/dist/cjs/onchain/market/adapters/abi/adapters/index.js +6 -0
- package/dist/cjs/onchain/market/adapters/abi/index.js +13 -0
- package/dist/cjs/onchain/market/adapters/abi/treehouse/iTreehouseFastlane.js +39 -0
- package/dist/cjs/onchain/market/adapters/abi/treehouse/iTreehouseRedemptionGateway.js +230 -0
- package/dist/cjs/onchain/market/adapters/abi/treehouse/iTreehouseRouter.js +29 -0
- package/dist/cjs/onchain/market/adapters/abi/treehouse/index.js +7 -0
- package/dist/cjs/onchain/market/adapters/contracts/TreehouseFastlaneAdapterContract.js +72 -0
- package/dist/cjs/onchain/market/adapters/contracts/TreehouseRedemptionGatewayAdapterContract.js +117 -0
- package/dist/cjs/onchain/market/adapters/contracts/TreehouseRouterAdapterContract.js +61 -0
- package/dist/cjs/onchain/market/adapters/contracts/index.js +6 -0
- package/dist/cjs/onchain/market/adapters/createAdapter.js +6 -0
- package/dist/cjs/onchain/market/adapters/index.js +18 -0
- package/dist/cjs/onchain/market/index.js +18 -0
- package/dist/cjs/permissionless/api/index.js +31 -0
- package/dist/cjs/permissionless/api/oracles/PermissionlessOracles.js +637 -65
- package/dist/cjs/permissionless/api/oracles/enrich.js +99 -0
- package/dist/cjs/permissionless/api/oracles/index.js +33 -0
- package/dist/cjs/permissionless/api/oracles/onchain-feed.js +25 -0
- package/dist/cjs/permissionless/api/oracles/schemas.js +84 -59
- package/dist/cjs/permissionless/api/oracles/score/formula.js +56 -0
- package/dist/cjs/permissionless/api/oracles/score/index.js +11 -0
- package/dist/cjs/permissionless/api/oracles/score/run.js +252 -0
- package/dist/cjs/permissionless/api/oracles/score/types.js +1 -0
- package/dist/cjs/permissionless/api/oracles/stress/classify.js +258 -0
- package/dist/cjs/permissionless/api/oracles/stress/index.js +24 -0
- package/dist/cjs/permissionless/api/oracles/stress/override.js +125 -0
- package/dist/cjs/permissionless/api/oracles/stress/run.js +485 -0
- package/dist/cjs/permissionless/api/oracles/stress/score.js +98 -0
- package/dist/cjs/permissionless/api/oracles/stress/types.js +1 -0
- package/dist/cjs/permissionless/api/schemas.js +0 -3
- package/dist/cjs/permissionless/api/transactions/InstanceOwnerTransactions.js +14 -3
- package/dist/cjs/permissionless/api/transactions/lp-price-feeds.js +10 -4
- package/dist/cjs/permissionless/bindings/bytecode-repository.js +8 -0
- package/dist/cjs/permissionless/bindings/governance/governor.js +1 -1
- package/dist/cjs/permissionless/bindings/index.js +8 -3
- package/dist/cjs/permissionless/bindings/kill-switch-proxy.js +188 -0
- package/dist/cjs/permissionless/bindings/market-configurator-factory.js +39 -1
- package/dist/cjs/permissionless/bindings/market-configurator.js +142 -0
- package/dist/cjs/permissionless/index.js +42 -3
- package/dist/cjs/permissionless/utils/index.js +3 -0
- package/dist/cjs/permissionless/utils/literals.js +2 -0
- package/dist/cjs/permissionless/utils/price-update/get-prices.js +64 -0
- package/dist/cjs/permissionless/utils/price-update/index.js +2 -0
- package/dist/cjs/testnets/TestnetsAPI.js +179 -0
- package/dist/cjs/testnets/errors/TestnetNotFoundError.js +15 -0
- package/dist/cjs/testnets/errors/TestnetRequestFailedError.js +24 -0
- package/dist/cjs/testnets/errors/TestnetStatusError.js +19 -0
- package/dist/cjs/testnets/errors/TestnetValidationError.js +24 -0
- package/dist/cjs/testnets/errors/TestnetWaitTimeoutError.js +30 -0
- package/dist/cjs/testnets/errors/TestnetsError.js +34 -0
- package/dist/cjs/testnets/errors/index.js +13 -0
- package/dist/cjs/testnets/index.js +32 -0
- package/dist/cjs/testnets/package.json +1 -0
- package/dist/cjs/testnets/schemas.js +242 -0
- package/dist/cjs/testnets/types.js +1 -0
- package/dist/esm/abi/310/iKillSwitchProxy.js +129 -0
- package/dist/esm/onchain/index.js +10 -1
- package/dist/esm/onchain/market/adapters/abi/adapters/iTreehouseFastlaneAdapter.js +112 -0
- package/dist/esm/onchain/market/adapters/abi/adapters/iTreehouseRedemptionGatewayAdapter.js +257 -0
- package/dist/esm/onchain/market/adapters/abi/adapters/iTreehouseRouterAdapter.js +160 -0
- package/dist/esm/onchain/market/adapters/abi/adapters/index.js +4 -1
- package/dist/esm/onchain/market/adapters/abi/index.js +8 -1
- package/dist/esm/onchain/market/adapters/abi/treehouse/iTreehouseFastlane.js +38 -0
- package/dist/esm/onchain/market/adapters/abi/treehouse/iTreehouseRedemptionGateway.js +229 -0
- package/dist/esm/onchain/market/adapters/abi/treehouse/iTreehouseRouter.js +28 -0
- package/dist/esm/onchain/market/adapters/abi/treehouse/index.js +4 -0
- package/dist/esm/onchain/market/adapters/contracts/TreehouseFastlaneAdapterContract.js +71 -0
- package/dist/esm/onchain/market/adapters/contracts/TreehouseRedemptionGatewayAdapterContract.js +116 -0
- package/dist/esm/onchain/market/adapters/contracts/TreehouseRouterAdapterContract.js +60 -0
- package/dist/esm/onchain/market/adapters/contracts/index.js +4 -1
- package/dist/esm/onchain/market/adapters/createAdapter.js +6 -0
- package/dist/esm/onchain/market/adapters/index.js +10 -1
- package/dist/esm/onchain/market/index.js +10 -1
- package/dist/esm/permissionless/api/index.js +7 -1
- package/dist/esm/permissionless/api/oracles/PermissionlessOracles.js +639 -67
- package/dist/esm/permissionless/api/oracles/enrich.js +94 -0
- package/dist/esm/permissionless/api/oracles/index.js +10 -1
- package/dist/esm/permissionless/api/oracles/onchain-feed.js +24 -0
- package/dist/esm/permissionless/api/oracles/schemas.js +80 -58
- package/dist/esm/permissionless/api/oracles/score/formula.js +51 -0
- package/dist/esm/permissionless/api/oracles/score/index.js +4 -0
- package/dist/esm/permissionless/api/oracles/score/run.js +250 -0
- package/dist/esm/permissionless/api/oracles/score/types.js +1 -0
- package/dist/esm/permissionless/api/oracles/stress/classify.js +256 -0
- package/dist/esm/permissionless/api/oracles/stress/index.js +6 -0
- package/dist/esm/permissionless/api/oracles/stress/override.js +119 -0
- package/dist/esm/permissionless/api/oracles/stress/run.js +478 -0
- package/dist/esm/permissionless/api/oracles/stress/score.js +95 -0
- package/dist/esm/permissionless/api/oracles/stress/types.js +1 -0
- package/dist/esm/permissionless/api/schemas.js +2 -4
- package/dist/esm/permissionless/api/transactions/InstanceOwnerTransactions.js +14 -3
- package/dist/esm/permissionless/api/transactions/lp-price-feeds.js +10 -4
- package/dist/esm/permissionless/bindings/bytecode-repository.js +8 -0
- package/dist/esm/permissionless/bindings/governance/governor.js +1 -1
- package/dist/esm/permissionless/bindings/index.js +5 -4
- package/dist/esm/permissionless/bindings/kill-switch-proxy.js +184 -0
- package/dist/esm/permissionless/bindings/market-configurator-factory.js +37 -0
- package/dist/esm/permissionless/bindings/market-configurator.js +143 -1
- package/dist/esm/permissionless/index.js +13 -6
- package/dist/esm/permissionless/utils/index.js +3 -3
- package/dist/esm/permissionless/utils/literals.js +2 -1
- package/dist/esm/permissionless/utils/price-update/get-prices.js +63 -1
- package/dist/esm/permissionless/utils/price-update/index.js +2 -2
- package/dist/esm/testnets/TestnetsAPI.js +178 -0
- package/dist/esm/testnets/errors/TestnetNotFoundError.js +14 -0
- package/dist/esm/testnets/errors/TestnetRequestFailedError.js +23 -0
- package/dist/esm/testnets/errors/TestnetStatusError.js +18 -0
- package/dist/esm/testnets/errors/TestnetValidationError.js +23 -0
- package/dist/esm/testnets/errors/TestnetWaitTimeoutError.js +29 -0
- package/dist/esm/testnets/errors/TestnetsError.js +33 -0
- package/dist/esm/testnets/errors/index.js +7 -0
- package/dist/esm/testnets/index.js +11 -0
- package/dist/esm/testnets/package.json +1 -0
- package/dist/esm/testnets/schemas.js +228 -0
- package/dist/esm/testnets/types.js +1 -0
- package/dist/types/abi/310/iKillSwitchProxy.d.ts +114 -0
- package/dist/types/onchain/index.d.ts +10 -1
- package/dist/types/onchain/market/adapters/abi/adapters/iTreehouseFastlaneAdapter.d.ts +101 -0
- package/dist/types/onchain/market/adapters/abi/adapters/iTreehouseRedemptionGatewayAdapter.d.ts +237 -0
- package/dist/types/onchain/market/adapters/abi/adapters/iTreehouseRouterAdapter.d.ts +146 -0
- package/dist/types/onchain/market/adapters/abi/adapters/index.d.ts +4 -1
- package/dist/types/onchain/market/adapters/abi/index.d.ts +8 -1
- package/dist/types/onchain/market/adapters/abi/treehouse/iTreehouseFastlane.d.ts +33 -0
- package/dist/types/onchain/market/adapters/abi/treehouse/iTreehouseRedemptionGateway.d.ts +207 -0
- package/dist/types/onchain/market/adapters/abi/treehouse/iTreehouseRouter.d.ts +27 -0
- package/dist/types/onchain/market/adapters/abi/treehouse/index.d.ts +4 -0
- package/dist/types/onchain/market/adapters/contracts/TreehouseFastlaneAdapterContract.d.ts +156 -0
- package/dist/types/onchain/market/adapters/contracts/TreehouseRedemptionGatewayAdapterContract.d.ts +481 -0
- package/dist/types/onchain/market/adapters/contracts/TreehouseRouterAdapterContract.d.ts +193 -0
- package/dist/types/onchain/market/adapters/contracts/index.d.ts +4 -1
- package/dist/types/onchain/market/adapters/index.d.ts +10 -1
- package/dist/types/onchain/market/index.d.ts +10 -1
- package/dist/types/permissionless/api/index.d.ts +10 -2
- package/dist/types/permissionless/api/oracles/PermissionlessOracles.d.ts +237 -47
- package/dist/types/permissionless/api/oracles/enrich.d.ts +59 -0
- package/dist/types/permissionless/api/oracles/index.d.ts +12 -2
- package/dist/types/permissionless/api/oracles/onchain-feed.d.ts +15 -0
- package/dist/types/permissionless/api/oracles/schemas.d.ts +147 -41
- package/dist/types/permissionless/api/oracles/score/formula.d.ts +36 -0
- package/dist/types/permissionless/api/oracles/score/index.d.ts +4 -0
- package/dist/types/permissionless/api/oracles/score/run.d.ts +42 -0
- package/dist/types/permissionless/api/oracles/score/types.d.ts +118 -0
- package/dist/types/permissionless/api/oracles/stress/classify.d.ts +57 -0
- package/dist/types/permissionless/api/oracles/stress/index.d.ts +6 -0
- package/dist/types/permissionless/api/oracles/stress/override.d.ts +59 -0
- package/dist/types/permissionless/api/oracles/stress/run.d.ts +100 -0
- package/dist/types/permissionless/api/oracles/stress/score.d.ts +57 -0
- package/dist/types/permissionless/api/oracles/stress/types.d.ts +272 -0
- package/dist/types/permissionless/api/oracles/types.d.ts +320 -125
- package/dist/types/permissionless/api/schemas.d.ts +0 -2
- package/dist/types/permissionless/api/transactions/lp-price-feeds.d.ts +9 -1
- package/dist/types/permissionless/bindings/bytecode-repository.d.ts +10 -1
- package/dist/types/permissionless/bindings/index.d.ts +4 -3
- package/dist/types/permissionless/bindings/kill-switch-proxy.d.ts +205 -0
- package/dist/types/permissionless/bindings/market-configurator-factory.d.ts +30 -0
- package/dist/types/permissionless/bindings/market-configurator.d.ts +30 -1
- package/dist/types/permissionless/bindings/types.d.ts +27 -0
- package/dist/types/permissionless/index.d.ts +15 -6
- package/dist/types/permissionless/utils/index.d.ts +3 -3
- package/dist/types/permissionless/utils/literals.d.ts +1 -0
- package/dist/types/permissionless/utils/price-update/get-prices.d.ts +33 -0
- package/dist/types/permissionless/utils/price-update/index.d.ts +2 -2
- package/dist/types/testnets/TestnetsAPI.d.ts +108 -0
- package/dist/types/testnets/errors/TestnetNotFoundError.d.ts +12 -0
- package/dist/types/testnets/errors/TestnetRequestFailedError.d.ts +11 -0
- package/dist/types/testnets/errors/TestnetStatusError.d.ts +15 -0
- package/dist/types/testnets/errors/TestnetValidationError.d.ts +16 -0
- package/dist/types/testnets/errors/TestnetWaitTimeoutError.d.ts +21 -0
- package/dist/types/testnets/errors/TestnetsError.d.ts +47 -0
- package/dist/types/testnets/errors/index.d.ts +7 -0
- package/dist/types/testnets/index.d.ts +11 -0
- package/dist/types/testnets/schemas.d.ts +554 -0
- package/dist/types/testnets/types.d.ts +717 -0
- package/package.json +6 -1
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_permissionless_api_oracles_stress_classify = require("../stress/classify.js");
|
|
3
|
+
const require_permissionless_api_oracles_stress_run = require("../stress/run.js");
|
|
4
|
+
const require_permissionless_api_oracles_score_formula = require("./formula.js");
|
|
5
|
+
let viem = require("viem");
|
|
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 lower = (address) => address.toLowerCase();
|
|
11
|
+
/**
|
|
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.
|
|
45
|
+
*
|
|
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 }) {
|
|
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));
|
|
51
|
+
const { sources, unresolved } = require_permissionless_api_oracles_stress_classify.classifySources(chainId, [...nodes.values()], targets, (target) => rateBaselines.get(target) ?? answers.get(target) ?? null);
|
|
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`);
|
|
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]);
|
|
62
|
+
}
|
|
63
|
+
return out;
|
|
64
|
+
};
|
|
65
|
+
const sourcesOf = (feed) => [...groupsOf(feed).keys()];
|
|
66
|
+
if (roots.length === 0) return {
|
|
67
|
+
results: [],
|
|
68
|
+
sourcesOf
|
|
69
|
+
};
|
|
70
|
+
const updatedAt = BigInt(Math.floor(Date.now() / 1e3));
|
|
71
|
+
const fresh = sources.filter((source) => drivable(source) && source.kind === "feed");
|
|
72
|
+
const asIs = await require_permissionless_api_oracles_stress_run.readUnder(client, roots, [], 1, updatedAt);
|
|
73
|
+
const stale = roots.filter((root) => (asIs.get(root) ?? null) === null);
|
|
74
|
+
const freshened = stale.length > 0 ? await require_permissionless_api_oracles_stress_run.readUnder(client, stale, [], 1, updatedAt, fresh) : /* @__PURE__ */ new Map();
|
|
75
|
+
const base = new Map(roots.map((root) => [root, asIs.get(root) ?? freshened.get(root) ?? null]));
|
|
76
|
+
const asIsRoots = roots.filter((root) => (asIs.get(root) ?? null) !== null);
|
|
77
|
+
const freshRoots = stale.filter((root) => (freshened.get(root) ?? null) !== null);
|
|
78
|
+
/** Every root under a lie, each read the way its base was. */
|
|
79
|
+
const readLying = async (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()]);
|
|
81
|
+
return new Map([...plain, ...refreshed]);
|
|
82
|
+
};
|
|
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);
|
|
88
|
+
const byGroup = /* @__PURE__ */ new Map();
|
|
89
|
+
for (const source of sources.filter(drivable)) byGroup.set(source.group, [...byGroup.get(source.group) ?? [], source]);
|
|
90
|
+
const reachedGroups = /* @__PURE__ */ new Set();
|
|
91
|
+
for (const root of roots) for (const group of groupsOf(root).keys()) reachedGroups.add(group);
|
|
92
|
+
const stressed = /* @__PURE__ */ new Map();
|
|
93
|
+
const queue = [...reachedGroups];
|
|
94
|
+
for (let i = 0; i < queue.length; i += CONCURRENCY) await Promise.all(queue.slice(i, i + CONCURRENCY).map(async (group) => {
|
|
95
|
+
stressed.set(group, await readLying(byGroup.get(group) ?? []));
|
|
96
|
+
}));
|
|
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
|
+
});
|
|
117
|
+
return {
|
|
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)
|
|
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
|
|
155
|
+
});
|
|
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);
|
|
179
|
+
const entry = byAsset.get(asset) ?? {
|
|
180
|
+
symbol: main.tokenSymbol,
|
|
181
|
+
markets: []
|
|
182
|
+
};
|
|
183
|
+
byAsset.set(asset, entry);
|
|
184
|
+
entry.markets.push({
|
|
185
|
+
market: main.market,
|
|
186
|
+
marketName: main.marketName,
|
|
187
|
+
marketConfigurator: main.marketConfigurator,
|
|
188
|
+
curatorName: main.curatorName,
|
|
189
|
+
main: side(main),
|
|
190
|
+
reserve: reserve ? side(reserve) : null,
|
|
191
|
+
...result
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
return [...byAsset].map(([asset, { symbol, markets }]) => {
|
|
195
|
+
const sorted = markets.sort((a, b) => b.score - a.score);
|
|
196
|
+
const worst = sorted[0] ?? null;
|
|
197
|
+
return {
|
|
198
|
+
chainId,
|
|
199
|
+
asset,
|
|
200
|
+
symbol,
|
|
201
|
+
score: worst?.score ?? null,
|
|
202
|
+
unverified: sorted.some((market) => market.unverified),
|
|
203
|
+
worst,
|
|
204
|
+
markets: sorted
|
|
205
|
+
};
|
|
206
|
+
});
|
|
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
|
+
}
|
|
250
|
+
//#endregion
|
|
251
|
+
exports.runAssetScores = runAssetScores;
|
|
252
|
+
exports.runFeedPairScore = runFeedPairScore;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region src/permissionless/api/oracles/stress/classify.ts
|
|
3
|
+
/**
|
|
4
|
+
* The feed types whose price is a protocol's exchange rate read off a
|
|
5
|
+
* contract, mapped to the group that rate belongs to.
|
|
6
|
+
*
|
|
7
|
+
* These are not leaves: an LP feed also reads the price of whatever the rate
|
|
8
|
+
* is denominated in, and that child is somebody else's source. What belongs to
|
|
9
|
+
* the protocol is the rate alone, which is why these are perturbed at the
|
|
10
|
+
* contract the feed reads it from rather than at the feed.
|
|
11
|
+
*
|
|
12
|
+
* `null` means the protocol is the vault's own and has to come from
|
|
13
|
+
* {@link VAULT_GROUPS} or, failing that, from the feed's description.
|
|
14
|
+
*/
|
|
15
|
+
const RATE_TYPE_GROUPS = {
|
|
16
|
+
WSTETH: "Lido",
|
|
17
|
+
CURVE_STABLE: "Curve",
|
|
18
|
+
CURVE_CRYPTO: "Curve",
|
|
19
|
+
CURVE_TWAP: "Curve",
|
|
20
|
+
YEARN: "Yearn",
|
|
21
|
+
MELLOW_LRT: "Mellow",
|
|
22
|
+
BALANCER_STABLE: "Balancer",
|
|
23
|
+
BALANCER_WEIGHTED: "Balancer",
|
|
24
|
+
ERC4626: null
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Which protocol an ERC-4626 vault belongs to, by the symbol its feed is
|
|
28
|
+
* described with.
|
|
29
|
+
*
|
|
30
|
+
* Curated, and deliberately short: a vault not named here becomes a group of
|
|
31
|
+
* one under its own symbol, which is still a correct partition — one vault is
|
|
32
|
+
* one source — and is flagged so a reader knows nothing claims it fails
|
|
33
|
+
* alongside anything else.
|
|
34
|
+
*/
|
|
35
|
+
const VAULT_GROUPS = {
|
|
36
|
+
sUSDS: "Sky",
|
|
37
|
+
sDAI: "Sky",
|
|
38
|
+
sUSDe: "Ethena",
|
|
39
|
+
sUSDf: "Falcon",
|
|
40
|
+
wstUSR: "Resolv",
|
|
41
|
+
syrupUSDC: "Maple",
|
|
42
|
+
hgETH: "Kelp",
|
|
43
|
+
rstETH: "Mellow",
|
|
44
|
+
Re7LRT: "Mellow",
|
|
45
|
+
pzETH: "Mellow",
|
|
46
|
+
DVstETH: "Mellow",
|
|
47
|
+
tETH: "Treehouse"
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* Rules over a Chainlink-shaped feed's own description, in order.
|
|
51
|
+
*
|
|
52
|
+
* Only reached for a leaf Gearbox did not deploy, which is where the provider
|
|
53
|
+
* is not already stated. The first match wins, so the specific patterns come
|
|
54
|
+
* before the general shape of a Chainlink pair.
|
|
55
|
+
*/
|
|
56
|
+
const DESCRIPTION_RULES = [
|
|
57
|
+
{
|
|
58
|
+
via: "description names Redstone",
|
|
59
|
+
match: /redstone/i,
|
|
60
|
+
group: "Redstone"
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
via: "description names Pyth",
|
|
64
|
+
match: /\bpyth\b/i,
|
|
65
|
+
group: "Pyth"
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
via: "description names Pendle",
|
|
69
|
+
match: /pendle/i,
|
|
70
|
+
group: "Pendle"
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
via: "description names Chronicle",
|
|
74
|
+
match: /chronicle/i,
|
|
75
|
+
group: "Chronicle"
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
via: "description names API3",
|
|
79
|
+
match: /\bapi3\b/i,
|
|
80
|
+
group: "API3"
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
via: "description names Chaos",
|
|
84
|
+
match: /chaos\s*labs/i,
|
|
85
|
+
group: "Chaos Labs"
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
via: "Midas naming convention",
|
|
89
|
+
match: /^m(TBILL|BASIS|EDGE|F-ONE|GLOBAL|Re7YIELD|[A-Z]{2,}[0-9]*)\s*\//,
|
|
90
|
+
group: "Midas"
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
via: "exchange-rate adapter, grouped by its token",
|
|
94
|
+
match: /^([A-Za-z0-9+]+)\s*[/-]\s*[A-Za-z0-9+]+\s+exchange\s+rate/i,
|
|
95
|
+
group: (m) => m[1]
|
|
96
|
+
}
|
|
97
|
+
];
|
|
98
|
+
/**
|
|
99
|
+
* Whether a description is a Chainlink pair: `BASE / QUOTE`, spaced, in the
|
|
100
|
+
* shape their aggregators have published for years.
|
|
101
|
+
*
|
|
102
|
+
* Paired with a version of 6, which is what `EACAggregatorProxy` answers and
|
|
103
|
+
* what the imitators — version 0 or 1, mostly — do not. Either alone would be
|
|
104
|
+
* wrong: plenty of adapters describe themselves as a spaced pair, and plenty
|
|
105
|
+
* of Chainlink feeds are read through a wrapper that reports another version.
|
|
106
|
+
*/
|
|
107
|
+
const CHAINLINK_PAIR = /^[A-Za-z0-9+]+ \/ [A-Za-z0-9+]+$/;
|
|
108
|
+
/** Feed types that are their own answer, with no source behind them at all. */
|
|
109
|
+
const SELF_CONTAINED = {
|
|
110
|
+
CONSTANT: "Constant",
|
|
111
|
+
ZERO: "Zero"
|
|
112
|
+
};
|
|
113
|
+
/** The type without the `PRICE_FEED::` the bytecode repository prefixes it with. */
|
|
114
|
+
function bareType(contractType) {
|
|
115
|
+
return contractType.replace(/^PRICE_FEED::/, "");
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Which group a leaf price feed belongs to, and how that was decided.
|
|
119
|
+
*
|
|
120
|
+
* The order is what makes it defensible: what Gearbox deployed says who it
|
|
121
|
+
* reads, so those types are taken first; then the description rules, most
|
|
122
|
+
* specific first; then the Chainlink shape; and anything left is its own group
|
|
123
|
+
* under its own description, flagged.
|
|
124
|
+
*/
|
|
125
|
+
function classifyLeaf(node) {
|
|
126
|
+
const type = bareType(node.contractType);
|
|
127
|
+
const selfContained = SELF_CONTAINED[type];
|
|
128
|
+
if (selfContained) return {
|
|
129
|
+
group: selfContained,
|
|
130
|
+
identified: true,
|
|
131
|
+
via: "Gearbox feed type"
|
|
132
|
+
};
|
|
133
|
+
if (type === "REDSTONE" || type === "PYTH" || type === "CHAINLINK") return {
|
|
134
|
+
group: type === "PYTH" ? "Pyth" : type === "REDSTONE" ? "Redstone" : "Chainlink",
|
|
135
|
+
identified: true,
|
|
136
|
+
via: "Gearbox feed type"
|
|
137
|
+
};
|
|
138
|
+
for (const rule of DESCRIPTION_RULES) {
|
|
139
|
+
const m = node.description.match(rule.match);
|
|
140
|
+
if (m) return {
|
|
141
|
+
group: typeof rule.group === "function" ? rule.group(m) : rule.group,
|
|
142
|
+
identified: true,
|
|
143
|
+
via: rule.via
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
if (CHAINLINK_PAIR.test(node.description) && node.version === 6) return {
|
|
147
|
+
group: "Chainlink",
|
|
148
|
+
identified: true,
|
|
149
|
+
via: "Chainlink pair description at interface version 6"
|
|
150
|
+
};
|
|
151
|
+
return {
|
|
152
|
+
group: node.description || node.address,
|
|
153
|
+
identified: false,
|
|
154
|
+
via: "unrecognised: grouped alone under its own description"
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Which group a rate-bearing feed's rate belongs to.
|
|
159
|
+
*
|
|
160
|
+
* The type names the protocol for everything but an ERC-4626 vault, where the
|
|
161
|
+
* protocol is the vault's own and is looked up by the symbol the feed is
|
|
162
|
+
* described with — `"sUSDS / USD LP price feed"` is Sky's.
|
|
163
|
+
*/
|
|
164
|
+
function classifyRate(node) {
|
|
165
|
+
const type = bareType(node.contractType);
|
|
166
|
+
const named = RATE_TYPE_GROUPS[type];
|
|
167
|
+
if (named) return {
|
|
168
|
+
group: named,
|
|
169
|
+
identified: true,
|
|
170
|
+
via: "Gearbox feed type"
|
|
171
|
+
};
|
|
172
|
+
const symbol = node.description.match(/^([A-Za-z0-9+.-]+)\s*\//)?.[1];
|
|
173
|
+
const protocol = symbol ? VAULT_GROUPS[symbol] : void 0;
|
|
174
|
+
if (protocol) return {
|
|
175
|
+
group: protocol,
|
|
176
|
+
identified: true,
|
|
177
|
+
via: "curated vault table"
|
|
178
|
+
};
|
|
179
|
+
return {
|
|
180
|
+
group: symbol ?? node.address,
|
|
181
|
+
identified: false,
|
|
182
|
+
via: "vault not in the curated table: grouped alone under its symbol"
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Every source behind a chain's feed trees.
|
|
187
|
+
*
|
|
188
|
+
* A source per contract, not per feed: two of an oracle's trees carrying the
|
|
189
|
+
* same aggregator is one source read twice, and the test varies it once.
|
|
190
|
+
*
|
|
191
|
+
* `rateTargets` says which contract each rate-bearing feed reads its rate
|
|
192
|
+
* from — the feed's own `lpContract`, read separately because the compressor
|
|
193
|
+
* does not report it. A feed missing from it is left out with a reason, since
|
|
194
|
+
* perturbing the whole feed instead would charge its protocol with a failure
|
|
195
|
+
* of whatever prices the thing the rate is denominated in.
|
|
196
|
+
**/
|
|
197
|
+
function classifySources(chainId, nodes, rateTargets, baselineOf) {
|
|
198
|
+
const sources = [];
|
|
199
|
+
const unresolved = [];
|
|
200
|
+
const seen = /* @__PURE__ */ new Set();
|
|
201
|
+
for (const node of nodes) {
|
|
202
|
+
const isRate = bareType(node.contractType) in RATE_TYPE_GROUPS;
|
|
203
|
+
if (!isRate && node.underlyingFeeds.length > 0) continue;
|
|
204
|
+
const target = isRate ? rateTargets.get(node.address) : node.address;
|
|
205
|
+
if (!target) {
|
|
206
|
+
unresolved.push(node);
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
if (seen.has(target)) continue;
|
|
210
|
+
seen.add(target);
|
|
211
|
+
sources.push({
|
|
212
|
+
chainId,
|
|
213
|
+
kind: isRate ? "rate" : "feed",
|
|
214
|
+
target,
|
|
215
|
+
feed: node.address,
|
|
216
|
+
contractType: node.contractType,
|
|
217
|
+
description: node.description,
|
|
218
|
+
perturbable: !(bareType(node.contractType) in SELF_CONTAINED),
|
|
219
|
+
baseline: baselineOf(target),
|
|
220
|
+
...isRate ? classifyRate(node) : classifyLeaf(node)
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
return {
|
|
224
|
+
sources,
|
|
225
|
+
unresolved
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* The sources each of the given feeds is priced through, following the trees
|
|
230
|
+
* down from every one of them.
|
|
231
|
+
*
|
|
232
|
+
* A feed is reached by a source when the source sits anywhere beneath it, at
|
|
233
|
+
* any depth: a market reading a composite of a composite of a Chainlink
|
|
234
|
+
* aggregator is a market Chainlink can move. Memoised over the whole forest,
|
|
235
|
+
* so a node shared by twenty trees is walked once.
|
|
236
|
+
**/
|
|
237
|
+
function sourcesByFeed(roots, nodes, sources) {
|
|
238
|
+
const sourceOfFeed = /* @__PURE__ */ new Map();
|
|
239
|
+
for (const source of sources) sourceOfFeed.set(source.feed, source.target);
|
|
240
|
+
const memo = /* @__PURE__ */ new Map();
|
|
241
|
+
const walk = (feed, path) => {
|
|
242
|
+
const done = memo.get(feed);
|
|
243
|
+
if (done) return done;
|
|
244
|
+
if (path.has(feed)) return /* @__PURE__ */ new Set();
|
|
245
|
+
const found = /* @__PURE__ */ new Set();
|
|
246
|
+
const own = sourceOfFeed.get(feed);
|
|
247
|
+
if (own) found.add(own);
|
|
248
|
+
path.add(feed);
|
|
249
|
+
for (const child of nodes.get(feed)?.underlyingFeeds ?? []) for (const source of walk(child, path)) found.add(source);
|
|
250
|
+
path.delete(feed);
|
|
251
|
+
memo.set(feed, found);
|
|
252
|
+
return found;
|
|
253
|
+
};
|
|
254
|
+
return new Map(roots.map((root) => [root, walk(root, /* @__PURE__ */ new Set())]));
|
|
255
|
+
}
|
|
256
|
+
//#endregion
|
|
257
|
+
exports.classifySources = classifySources;
|
|
258
|
+
exports.sourcesByFeed = sourcesByFeed;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_permissionless_api_oracles_stress_classify = require("./classify.js");
|
|
3
|
+
const require_permissionless_api_oracles_stress_override = require("./override.js");
|
|
4
|
+
const require_permissionless_api_oracles_stress_score = require("./score.js");
|
|
5
|
+
const require_permissionless_api_oracles_stress_run = require("./run.js");
|
|
6
|
+
require("./types.js");
|
|
7
|
+
exports.DEFAULT_SCENARIOS = require_permissionless_api_oracles_stress_run.DEFAULT_SCENARIOS;
|
|
8
|
+
exports.RATE_FEED_TYPE = require_permissionless_api_oracles_stress_run.RATE_FEED_TYPE;
|
|
9
|
+
exports.UNBOUNDED_DOWN = require_permissionless_api_oracles_stress_override.UNBOUNDED_DOWN;
|
|
10
|
+
exports.UNBOUNDED_UP = require_permissionless_api_oracles_stress_override.UNBOUNDED_UP;
|
|
11
|
+
exports.classifySources = require_permissionless_api_oracles_stress_classify.classifySources;
|
|
12
|
+
exports.groupOverride = require_permissionless_api_oracles_stress_override.groupOverride;
|
|
13
|
+
exports.perturb = require_permissionless_api_oracles_stress_override.perturb;
|
|
14
|
+
exports.rateStub = require_permissionless_api_oracles_stress_override.rateStub;
|
|
15
|
+
exports.readFeedTrees = require_permissionless_api_oracles_stress_run.readFeedTrees;
|
|
16
|
+
exports.readForest = require_permissionless_api_oracles_stress_run.readForest;
|
|
17
|
+
exports.readRateTargets = require_permissionless_api_oracles_stress_run.readRateTargets;
|
|
18
|
+
exports.readUnder = require_permissionless_api_oracles_stress_run.readUnder;
|
|
19
|
+
exports.roundDataStub = require_permissionless_api_oracles_stress_override.roundDataStub;
|
|
20
|
+
exports.runStressTest = require_permissionless_api_oracles_stress_run.runStressTest;
|
|
21
|
+
exports.scoreGroup = require_permissionless_api_oracles_stress_score.scoreGroup;
|
|
22
|
+
exports.slotHarm = require_permissionless_api_oracles_stress_score.slotHarm;
|
|
23
|
+
exports.sourcesByFeed = require_permissionless_api_oracles_stress_classify.sourcesByFeed;
|
|
24
|
+
exports.verdictOf = require_permissionless_api_oracles_stress_score.verdictOf;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region src/permissionless/api/oracles/stress/override.ts
|
|
3
|
+
/**
|
|
4
|
+
* Making a contract lie for the length of one `eth_call`.
|
|
5
|
+
*
|
|
6
|
+
* No fork and no simulation harness: an `eth_call` carries a state override,
|
|
7
|
+
* so the aggregator's code is replaced for that call alone and everything
|
|
8
|
+
* above it — the composite feeds, their bounds, the oracle's own checks — runs
|
|
9
|
+
* as deployed, on real state, at the current block. What comes back is what
|
|
10
|
+
* the market would have read.
|
|
11
|
+
**/
|
|
12
|
+
/** A word, as the 32 bytes `PUSH32` wants, two's complement for negatives. */
|
|
13
|
+
function word(value) {
|
|
14
|
+
return BigInt.asUintN(256, value).toString(16).padStart(64, "0");
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Runtime code answering every call with the same five words.
|
|
18
|
+
*
|
|
19
|
+
* Deliberately selector-blind. A price feed is read through more than
|
|
20
|
+
* `latestRoundData` — `decimals`, `version`, sometimes `latestAnswer` — and
|
|
21
|
+
* dispatching on each would mean knowing which of them every caller in a tree
|
|
22
|
+
* reaches for. Five words decode as the round data; a caller expecting one
|
|
23
|
+
* word reads the first, which is why `roundId` is set to the 8 decimals every
|
|
24
|
+
* Gearbox feed reports rather than to a round number.
|
|
25
|
+
*
|
|
26
|
+
* `answeredInRound` is the same 8, so the `answeredInRound >= roundId` check
|
|
27
|
+
* the older consumers make passes.
|
|
28
|
+
**/
|
|
29
|
+
function roundDataStub(answer, updatedAt) {
|
|
30
|
+
return `0x${[
|
|
31
|
+
`7f${word(8n)}600052`,
|
|
32
|
+
`7f${word(answer)}602052`,
|
|
33
|
+
`7f${word(updatedAt)}604052`,
|
|
34
|
+
`7f${word(updatedAt)}606052`,
|
|
35
|
+
`7f${word(8n)}608052`,
|
|
36
|
+
"60a06000f3"
|
|
37
|
+
].join("")}`;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Runtime code answering every call with one word.
|
|
41
|
+
*
|
|
42
|
+
* What an exchange rate getter returns, whichever of them the feed calls:
|
|
43
|
+
* `stEthPerToken`, `convertToAssets`, `get_virtual_price`, `pricePerShare`
|
|
44
|
+
* and `getRate` all answer a single `uint256`, so the stub does not need to
|
|
45
|
+
* know which.
|
|
46
|
+
**/
|
|
47
|
+
function rateStub(rate) {
|
|
48
|
+
return `0x7f${word(rate)}60005260206000f3`;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* The two unbounded ends, as far as the types involved can carry them.
|
|
52
|
+
*
|
|
53
|
+
* "Infinitely large" has to stop somewhere: a price is an `int256` and the
|
|
54
|
+
* arithmetic above it multiplies, so a genuinely enormous answer overflows
|
|
55
|
+
* inside the composite feed and reverts — which would be scored as the
|
|
56
|
+
* oracle resisting, when all that happened is that the test asked something
|
|
57
|
+
* the type cannot hold. `1e12` times the current answer is past any bound,
|
|
58
|
+
* any sanity check and any collateral ratio, and still multiplies.
|
|
59
|
+
*/
|
|
60
|
+
const UNBOUNDED_UP = 0xe8d4a51000;
|
|
61
|
+
/** The other end: a thousandth of a trillionth, which is as near zero. */
|
|
62
|
+
const UNBOUNDED_DOWN = 1e-12;
|
|
63
|
+
/**
|
|
64
|
+
* A double as an exact integer ratio.
|
|
65
|
+
*
|
|
66
|
+
* Multipliers here span twenty-four orders of magnitude, so the obvious
|
|
67
|
+
* `BigInt(Math.round(m * 1e9))` is not available: at the top of the range the
|
|
68
|
+
* product is past what a double carries exactly and the multiplier quietly
|
|
69
|
+
* stops being the one that was asked for. Going through the decimal expansion
|
|
70
|
+
* keeps all fifteen digits a double has and puts the magnitude in the
|
|
71
|
+
* exponent, where it costs nothing.
|
|
72
|
+
*/
|
|
73
|
+
function asRatio(multiplier) {
|
|
74
|
+
const [mantissa = "0", exponent = "0"] = multiplier.toExponential(14).split("e");
|
|
75
|
+
const digits = BigInt(mantissa.replace(".", ""));
|
|
76
|
+
const power = Number(exponent) - 14;
|
|
77
|
+
return power >= 0 ? {
|
|
78
|
+
num: digits * 10n ** BigInt(power),
|
|
79
|
+
den: 1n
|
|
80
|
+
} : {
|
|
81
|
+
num: digits,
|
|
82
|
+
den: 10n ** BigInt(-power)
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* What a source should answer under a multiplier, given what it answers now.
|
|
87
|
+
*
|
|
88
|
+
* Floored at one rather than at zero: a multiplier small enough to round the
|
|
89
|
+
* baseline away would be testing a feed reporting nothing, which is the revert
|
|
90
|
+
* case and is a different scenario with a different meaning.
|
|
91
|
+
*/
|
|
92
|
+
function perturb(baseline, multiplier) {
|
|
93
|
+
const { num, den } = asRatio(Number.isFinite(multiplier) ? Math.min(Math.max(multiplier, UNBOUNDED_DOWN), UNBOUNDED_UP) : multiplier > 0 ? UNBOUNDED_UP : UNBOUNDED_DOWN);
|
|
94
|
+
const out = baseline * num / den;
|
|
95
|
+
return out === 0n ? 1n : out;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* The state override that makes every source of a group answer a multiple of
|
|
99
|
+
* what it answers now.
|
|
100
|
+
*
|
|
101
|
+
* One override per contract, so a group whose sources share an aggregator
|
|
102
|
+
* perturbs it once. Sources with nothing behind them and sources that are not
|
|
103
|
+
* answering are both left out, for the reasons on
|
|
104
|
+
* {@link StressSource.perturbable} and {@link StressSource.baseline} — an
|
|
105
|
+
* empty result is a group there is nothing to simulate about.
|
|
106
|
+
**/
|
|
107
|
+
function groupOverride(sources, multiplier, updatedAt) {
|
|
108
|
+
const overrides = [];
|
|
109
|
+
for (const source of sources) {
|
|
110
|
+
if (!source.perturbable || source.baseline === null) continue;
|
|
111
|
+
const value = perturb(source.baseline, multiplier);
|
|
112
|
+
overrides.push({
|
|
113
|
+
address: source.target,
|
|
114
|
+
code: source.kind === "rate" ? rateStub(value) : roundDataStub(value, updatedAt)
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
return overrides;
|
|
118
|
+
}
|
|
119
|
+
//#endregion
|
|
120
|
+
exports.UNBOUNDED_DOWN = UNBOUNDED_DOWN;
|
|
121
|
+
exports.UNBOUNDED_UP = UNBOUNDED_UP;
|
|
122
|
+
exports.groupOverride = groupOverride;
|
|
123
|
+
exports.perturb = perturb;
|
|
124
|
+
exports.rateStub = rateStub;
|
|
125
|
+
exports.roundDataStub = roundDataStub;
|