@gearbox-protocol/sdk 18.2.0-next.1 → 18.2.0-next.11
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/compressors/subcompressors/liquidation/treehouseLiquidationSubcompressor.js +179 -0
- package/dist/cjs/abi/compressors/subcompressors/withdrawal/treehouseWithdrawalSubcompressor.js +534 -0
- package/dist/cjs/abi/router/treehouseWorker.js +885 -0
- package/dist/cjs/dev/AccountOpener.js +1 -1
- package/dist/cjs/dev/withdrawalUtils.js +1 -1
- package/dist/cjs/history/trace/extractTransfers.js +1 -1
- package/dist/cjs/onchain/accounts/CreditAccountsServiceV310.js +2 -2
- package/dist/cjs/onchain/accounts/liquidations/LiquidationsService.js +1 -1
- package/dist/cjs/onchain/accounts/withdrawal-compressor/RedemptionLoggerV310Contract.js +1 -2
- package/dist/cjs/onchain/accounts/withdrawal-compressor/WithdrawalCompressorV310Contract.js +1 -2
- package/dist/cjs/onchain/accounts/withdrawal-compressor/WithdrawalCompressorV311Contract.js +1 -2
- package/dist/cjs/onchain/accounts/withdrawal-compressor/WithdrawalCompressorV313Contract.js +1 -1
- package/dist/cjs/onchain/base/TokensMeta.js +3 -3
- package/dist/cjs/onchain/core/createAddressProvider.js +1 -1
- package/dist/cjs/onchain/market/adapters/abi/actionAbi.js +1 -0
- package/dist/cjs/onchain/market/adapters/abi/conctructorAbi.js +3 -0
- package/dist/cjs/onchain/market/adapters/contracts/AccountMigratorAdapterContract.js +1 -1
- package/dist/cjs/onchain/market/adapters/contracts/ERC4626AdapterContract.js +1 -1
- package/dist/cjs/onchain/market/adapters/types.js +3 -0
- package/dist/cjs/onchain/market/credit/CreditFacadeV310BaseContract.js +1 -1
- package/dist/cjs/onchain/market/pool/PoolV310Contract.js +1 -1
- package/dist/cjs/onchain/market/rwa/createDegenNFT.js +1 -1
- package/dist/cjs/onchain/market/zapper/IETHZapperContract.js +1 -2
- package/dist/cjs/onchain/market/zapper/ZapperContract.js +1 -1
- package/dist/cjs/onchain/preview/preview/previewPoolPositionOperation.js +2 -2
- package/dist/cjs/onchain/utils/viem/simulateWithPriceUpdates.js +1 -1
- package/dist/cjs/permissionless/api/index.js +26 -0
- package/dist/cjs/permissionless/api/oracles/PermissionlessOracles.js +585 -47
- package/dist/cjs/permissionless/api/oracles/enrich.js +99 -0
- package/dist/cjs/permissionless/api/oracles/index.js +28 -0
- package/dist/cjs/permissionless/api/oracles/schemas.js +102 -35
- package/dist/cjs/permissionless/api/oracles/score/formula.js +99 -0
- package/dist/cjs/permissionless/api/oracles/score/index.js +8 -0
- package/dist/cjs/permissionless/api/oracles/score/run.js +88 -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 +22 -0
- package/dist/cjs/permissionless/api/oracles/stress/override.js +125 -0
- package/dist/cjs/permissionless/api/oracles/stress/run.js +432 -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/bindings/index.js +1 -1
- package/dist/cjs/permissionless/index.js +29 -1
- package/dist/cjs/permissionless/utils/index.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/compressors/subcompressors/liquidation/treehouseLiquidationSubcompressor.js +178 -0
- package/dist/esm/abi/compressors/subcompressors/withdrawal/treehouseWithdrawalSubcompressor.js +533 -0
- package/dist/esm/abi/router/treehouseWorker.js +884 -0
- package/dist/esm/onchain/market/adapters/abi/actionAbi.js +1 -0
- package/dist/esm/onchain/market/adapters/abi/conctructorAbi.js +3 -0
- package/dist/esm/onchain/market/adapters/types.js +3 -0
- package/dist/esm/permissionless/api/index.js +7 -1
- package/dist/esm/permissionless/api/oracles/PermissionlessOracles.js +587 -49
- 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/schemas.js +95 -35
- package/dist/esm/permissionless/api/oracles/score/formula.js +96 -0
- package/dist/esm/permissionless/api/oracles/score/index.js +4 -0
- package/dist/esm/permissionless/api/oracles/score/run.js +87 -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 +427 -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/bindings/index.js +1 -1
- package/dist/esm/permissionless/index.js +9 -3
- package/dist/esm/permissionless/utils/index.js +2 -2
- 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/compressors/subcompressors/liquidation/treehouseLiquidationSubcompressor.d.ts +148 -0
- package/dist/types/abi/compressors/subcompressors/withdrawal/treehouseWithdrawalSubcompressor.d.ts +440 -0
- package/dist/types/abi/router/treehouseWorker.d.ts +728 -0
- package/dist/types/onchain/market/adapters/types.d.ts +4 -1
- package/dist/types/permissionless/api/index.d.ts +10 -2
- package/dist/types/permissionless/api/oracles/PermissionlessOracles.d.ts +232 -35
- 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/schemas.d.ts +209 -22
- package/dist/types/permissionless/api/oracles/score/formula.d.ts +46 -0
- package/dist/types/permissionless/api/oracles/score/index.d.ts +4 -0
- package/dist/types/permissionless/api/oracles/score/run.d.ts +21 -0
- package/dist/types/permissionless/api/oracles/score/types.d.ts +85 -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 +73 -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 +408 -63
- package/dist/types/permissionless/api/schemas.d.ts +0 -2
- package/dist/types/permissionless/index.d.ts +11 -3
- package/dist/types/permissionless/utils/index.d.ts +2 -2
- 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 +9 -3
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { formatUnits } from "viem";
|
|
2
|
+
//#region src/permissionless/api/oracles/enrich.ts
|
|
3
|
+
/**
|
|
4
|
+
* What the oracles namespace works out for itself once the backend has
|
|
5
|
+
* answered.
|
|
6
|
+
*
|
|
7
|
+
* All of it used to be the backend's, and all of it needs the chain: a quota
|
|
8
|
+
* is denominated in its market's own underlying and a market cap is a supply
|
|
9
|
+
* times a price, so none of these can be indexed — they can only be read at
|
|
10
|
+
* the block the reader is on. Keeping them here means one description of each
|
|
11
|
+
* rule rather than one per service that wants it, and a backend that holds no
|
|
12
|
+
* viem client at all.
|
|
13
|
+
**/
|
|
14
|
+
/** Identifies a feed across chains, since an address alone does not. */
|
|
15
|
+
function feedKey(chainId, address) {
|
|
16
|
+
return `${chainId}:${address.toLowerCase()}`;
|
|
17
|
+
}
|
|
18
|
+
/** Prices looked up the way {@link totalQuotas} and the caps need them. */
|
|
19
|
+
function pricesByFeed(prices) {
|
|
20
|
+
return new Map(prices.map(({ chainId, address, price }) => [feedKey(chainId, address), price]));
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* The asset's quota across its markets, totalled in USD.
|
|
24
|
+
*
|
|
25
|
+
* Each market's figures are converted at the price that market's own oracle
|
|
26
|
+
* holds for its underlying, which is the same number that market's accounting
|
|
27
|
+
* uses.
|
|
28
|
+
*
|
|
29
|
+
* Limits count only the markets still running, because a shut down market's
|
|
30
|
+
* limit is not borrowable; what is already quoted counts everywhere, because
|
|
31
|
+
* debt outstanding is exposure either way. A market whose underlying could not
|
|
32
|
+
* be priced is skipped, which makes both figures floors, and `null` is nothing
|
|
33
|
+
* to total rather than a zero.
|
|
34
|
+
*
|
|
35
|
+
* @param priceOf - The price each underlying feed reports, in the 8 decimals
|
|
36
|
+
* every Gearbox feed uses.
|
|
37
|
+
*/
|
|
38
|
+
function totalQuotas(quotas, priceOf) {
|
|
39
|
+
let limit = 0;
|
|
40
|
+
let used = 0;
|
|
41
|
+
let anyLimit = false;
|
|
42
|
+
let anyUsed = false;
|
|
43
|
+
for (const quota of quotas) {
|
|
44
|
+
const raw = quota.underlyingPriceFeed === null ? null : priceOf(quota.underlyingPriceFeed);
|
|
45
|
+
if (raw === null || raw === void 0) continue;
|
|
46
|
+
const price = Number(formatUnits(raw, 8));
|
|
47
|
+
if (quota.quotaUsed !== null) {
|
|
48
|
+
used += quota.quotaUsed * price;
|
|
49
|
+
anyUsed = true;
|
|
50
|
+
}
|
|
51
|
+
if (quota.quotaLimit !== null && !quota.isShutdown) {
|
|
52
|
+
limit += quota.quotaLimit * price;
|
|
53
|
+
anyLimit = true;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
quotaLimitUsd: anyLimit ? limit : null,
|
|
58
|
+
quotaUsedUsd: anyUsed ? used : null
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* What one feed values one asset's supply at: `price × totalSupply`.
|
|
63
|
+
*
|
|
64
|
+
* Multiplied as integers and formatted once, rather than through two floats:
|
|
65
|
+
* an 8-decimal price times an 18-decimal supply loses its small end to a JS
|
|
66
|
+
* number long before it loses the large one, and the figure is a total rather
|
|
67
|
+
* than an estimate.
|
|
68
|
+
*
|
|
69
|
+
* `null` if either side could not be read — an asset nobody could price is
|
|
70
|
+
* not an asset worth nothing.
|
|
71
|
+
*/
|
|
72
|
+
function toCap(price, totalSupply, decimals) {
|
|
73
|
+
if (price === null || price === void 0 || totalSupply === null || totalSupply === void 0) return null;
|
|
74
|
+
return formatUnits(price * totalSupply, 8 + decimals);
|
|
75
|
+
}
|
|
76
|
+
/** Larger first, with `null` after every figure including a real zero. */
|
|
77
|
+
function descending(a, b) {
|
|
78
|
+
return (b ?? -1) - (a ?? -1);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Assets ordered the way a reader scans them: what the protocol is built on
|
|
82
|
+
* first, then what is actually borrowed against it, then what it is merely
|
|
83
|
+
* allowed to be borrowed against.
|
|
84
|
+
*
|
|
85
|
+
* Applied here and not by the backend because the two middle terms are the
|
|
86
|
+
* converted quotas, which only exist once the chain has been read. The
|
|
87
|
+
* backend orders by the first term and the tie-breakers, so a caller reading
|
|
88
|
+
* the route directly still gets a stable order; this refines it.
|
|
89
|
+
*/
|
|
90
|
+
function byCentrality(a, b) {
|
|
91
|
+
return b.underlyingMarketCount - a.underlyingMarketCount || descending(a.quotaUsedUsd, b.quotaUsedUsd) || descending(a.quotaLimitUsd, b.quotaLimitUsd) || a.symbol.localeCompare(b.symbol) || a.address.localeCompare(b.address) || a.chainId - b.chainId;
|
|
92
|
+
}
|
|
93
|
+
//#endregion
|
|
94
|
+
export { byCentrality, feedKey, pricesByFeed, toCap, totalQuotas };
|
|
@@ -1,2 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
import { classifySources, sourcesByFeed } from "./stress/classify.js";
|
|
2
|
+
import { UNBOUNDED_DOWN, UNBOUNDED_UP, groupOverride, perturb, rateStub, roundDataStub } from "./stress/override.js";
|
|
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";
|
|
6
|
+
import { runAssetScores } from "./score/run.js";
|
|
7
|
+
import { GET_PRICES_CHUNK_SIZE, GET_PRICES_GAS_LIMIT_BY_CHAIN_ID, PermissionlessOracles } from "./PermissionlessOracles.js";
|
|
8
|
+
import "./score/index.js";
|
|
9
|
+
import "./stress/index.js";
|
|
2
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 };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { chainSchema } from "../chains/schemas.js";
|
|
2
|
+
import { addressSchema } from "../schemas.js";
|
|
2
3
|
import { z } from "zod/v4";
|
|
3
4
|
//#region src/permissionless/api/oracles/schemas.ts
|
|
4
5
|
/**
|
|
@@ -18,62 +19,121 @@ const priceFeedSchema = z.looseObject({
|
|
|
18
19
|
stalenessPeriod: z.number(),
|
|
19
20
|
name: z.string()
|
|
20
21
|
});
|
|
21
|
-
/**
|
|
22
|
-
|
|
22
|
+
/**
|
|
23
|
+
* {@link PermissionlessPriceFeedAsset}
|
|
24
|
+
*
|
|
25
|
+
* What identifies an asset, which is all a feed's own listing of one says.
|
|
26
|
+
**/
|
|
27
|
+
const priceFeedAssetSchema = z.looseObject({
|
|
23
28
|
address: addressSchema,
|
|
24
29
|
symbol: z.string(),
|
|
25
30
|
name: z.string(),
|
|
26
|
-
decimals: z.number()
|
|
31
|
+
decimals: z.number()
|
|
32
|
+
});
|
|
33
|
+
/** {@link PermissionlessAsset} — identified as above, plus what prices it. */
|
|
34
|
+
const assetSchema = priceFeedAssetSchema.extend({
|
|
27
35
|
owner: addressSchema,
|
|
28
36
|
priceFeeds: z.array(priceFeedSchema)
|
|
29
37
|
});
|
|
30
|
-
/**
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
38
|
+
/**
|
|
39
|
+
* {@link PermissionlessPriceFeedStore}
|
|
40
|
+
*
|
|
41
|
+
* A store is a chain's, and is described by the same five fields the chain
|
|
42
|
+
* list answers with, so those are extended rather than restated.
|
|
43
|
+
**/
|
|
44
|
+
const priceFeedStoreSchema = chainSchema.extend({
|
|
37
45
|
assets: z.array(assetSchema),
|
|
38
46
|
priceFeeds: z.array(priceFeedSchema)
|
|
39
47
|
});
|
|
40
|
-
/** {@link
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
decimals: z.number()
|
|
52
|
-
});
|
|
53
|
-
/** {@link PermissionlessPriceFeedMarket} */
|
|
54
|
-
const priceFeedMarketSchema = z.looseObject({
|
|
48
|
+
/** Every chain's {@link PermissionlessPriceFeedStore}, one per chain. */
|
|
49
|
+
const priceFeedStoresSchema = z.array(priceFeedStoreSchema);
|
|
50
|
+
/**
|
|
51
|
+
* What names a market, whichever side it is being listed from.
|
|
52
|
+
*
|
|
53
|
+
* A feed's markets and an asset's markets are different reads, but they
|
|
54
|
+
* answer with the same market: who curates it, what it is denominated in and
|
|
55
|
+
* how much of the quota is taken. Only what the read is about differs, which
|
|
56
|
+
* is what each of the two below adds.
|
|
57
|
+
**/
|
|
58
|
+
const marketSchema = z.looseObject({
|
|
55
59
|
market: addressSchema,
|
|
56
60
|
marketConfigurator: addressSchema,
|
|
57
61
|
curatorName: z.string().nullable(),
|
|
58
62
|
marketName: z.string().nullable(),
|
|
63
|
+
underlyingSymbol: z.string().nullable(),
|
|
64
|
+
isUnderlying: z.boolean(),
|
|
65
|
+
quotaLimit: z.number().nullable(),
|
|
66
|
+
quotaUsed: z.number().nullable()
|
|
67
|
+
});
|
|
68
|
+
/**
|
|
69
|
+
* {@link PermissionlessIndexedPriceFeedMarket} — the market as the feed's slot
|
|
70
|
+
* in it, before the oracle is asked what period it holds for the slot.
|
|
71
|
+
**/
|
|
72
|
+
const priceFeedMarketSchema = marketSchema.extend({
|
|
59
73
|
token: addressSchema,
|
|
60
74
|
tokenSymbol: z.string().nullable(),
|
|
61
|
-
underlyingSymbol: z.string().nullable(),
|
|
62
75
|
reserve: z.boolean(),
|
|
63
|
-
|
|
64
|
-
|
|
76
|
+
priceOracle: addressSchema
|
|
77
|
+
});
|
|
78
|
+
/**
|
|
79
|
+
* {@link PermissionlessMarketSlot} — the same slot read from the chain's end,
|
|
80
|
+
* so the feed is a column rather than what was asked for.
|
|
81
|
+
**/
|
|
82
|
+
const marketSlotSchema = priceFeedMarketSchema.extend({
|
|
83
|
+
chainId: z.number(),
|
|
84
|
+
underlyingToken: addressSchema,
|
|
85
|
+
isShutdown: z.boolean(),
|
|
86
|
+
priceFeed: addressSchema,
|
|
87
|
+
priceFeedName: z.string().nullable(),
|
|
88
|
+
priceFeedInStore: z.boolean(),
|
|
89
|
+
underlyingPriceFeed: addressSchema.nullable()
|
|
65
90
|
});
|
|
66
|
-
/**
|
|
91
|
+
/** Every slot in scope, across whichever chains the read covered. */
|
|
92
|
+
const marketSlotsSchema = z.array(marketSlotSchema);
|
|
93
|
+
/** {@link PermissionlessIndexedPriceFeedDetails} */
|
|
67
94
|
const priceFeedDetailsSchema = z.looseObject({
|
|
68
95
|
chainId: z.number(),
|
|
69
96
|
priceFeed: priceFeedSchema,
|
|
70
97
|
assets: z.array(priceFeedAssetSchema),
|
|
71
98
|
markets: z.array(priceFeedMarketSchema)
|
|
72
99
|
});
|
|
73
|
-
/** {@link
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
100
|
+
/** {@link PermissionlessAssetMarketFeed} */
|
|
101
|
+
const assetMarketFeedSchema = z.looseObject({
|
|
102
|
+
address: addressSchema,
|
|
103
|
+
name: z.string().nullable(),
|
|
104
|
+
inStore: z.boolean()
|
|
105
|
+
});
|
|
106
|
+
/** {@link PermissionlessAssetMarket} — the market as what prices the asset in it. */
|
|
107
|
+
const assetMarketSchema = marketSchema.extend({
|
|
108
|
+
underlyingToken: addressSchema,
|
|
109
|
+
underlyingPriceFeed: addressSchema.nullable(),
|
|
110
|
+
isShutdown: z.boolean(),
|
|
111
|
+
mainFeed: assetMarketFeedSchema.nullable(),
|
|
112
|
+
reserveFeed: assetMarketFeedSchema.nullable()
|
|
113
|
+
});
|
|
114
|
+
/** {@link PermissionlessIndexedAssetDetails} */
|
|
115
|
+
const assetDetailsSchema = z.looseObject({
|
|
116
|
+
chainId: z.number(),
|
|
117
|
+
asset: priceFeedAssetSchema,
|
|
118
|
+
priceFeeds: z.array(priceFeedSchema),
|
|
119
|
+
markets: z.array(assetMarketSchema)
|
|
120
|
+
});
|
|
121
|
+
/** {@link PermissionlessAssetQuota} */
|
|
122
|
+
const assetQuotaSchema = z.looseObject({
|
|
123
|
+
quotaLimit: z.number().nullable(),
|
|
124
|
+
quotaUsed: z.number().nullable(),
|
|
125
|
+
isShutdown: z.boolean(),
|
|
126
|
+
underlyingPriceFeed: addressSchema.nullable()
|
|
127
|
+
});
|
|
128
|
+
/** {@link PermissionlessIndexedAssetSummary} */
|
|
129
|
+
const assetSummarySchema = z.looseObject({
|
|
130
|
+
chainId: z.number(),
|
|
131
|
+
address: addressSchema,
|
|
132
|
+
symbol: z.string(),
|
|
133
|
+
underlyingMarketCount: z.number(),
|
|
134
|
+
marketQuotas: z.array(assetQuotaSchema)
|
|
77
135
|
});
|
|
136
|
+
/** The list of {@link PermissionlessAssetSummary}, already ordered. */
|
|
137
|
+
const assetSummariesSchema = z.array(assetSummarySchema);
|
|
78
138
|
//#endregion
|
|
79
|
-
export { assetSchema, priceFeedAssetSchema, priceFeedDetailsSchema,
|
|
139
|
+
export { assetDetailsSchema, assetMarketFeedSchema, assetMarketSchema, assetQuotaSchema, assetSchema, assetSummariesSchema, assetSummarySchema, marketSlotSchema, marketSlotsSchema, priceFeedAssetSchema, priceFeedDetailsSchema, priceFeedMarketSchema, priceFeedSchema, priceFeedStoreSchema, priceFeedStoresSchema };
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
//#region src/permissionless/api/oracles/score/formula.ts
|
|
2
|
+
/**
|
|
3
|
+
* How well a market guards an asset's price, from its two feeds alone.
|
|
4
|
+
*
|
|
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.
|
|
18
|
+
*/
|
|
19
|
+
const INDEPENDENCE_WEIGHT = 6;
|
|
20
|
+
const BOUND_WEIGHT = 5;
|
|
21
|
+
/**
|
|
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.
|
|
24
|
+
*/
|
|
25
|
+
const BOUNDED_TYPE = /::(BOUNDED|ERC4626|CURVE_\w+|WSTETH|YEARN|MELLOW\w*|BALANCER\w*)$/;
|
|
26
|
+
const ZERO_TYPE = "PRICE_FEED::ZERO";
|
|
27
|
+
/**
|
|
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.
|
|
32
|
+
*/
|
|
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;
|
|
39
|
+
}
|
|
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;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
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.
|
|
51
|
+
*/
|
|
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)));
|
|
76
|
+
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
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
//#endregion
|
|
96
|
+
export { BOUND_WEIGHT, INDEPENDENCE_WEIGHT, scoreMarket };
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { classifySources, sourcesByFeed } from "../stress/classify.js";
|
|
2
|
+
import { RATE_FEED_TYPE, readForest, readRateTargets } from "../stress/run.js";
|
|
3
|
+
import { scoreMarket } from "./formula.js";
|
|
4
|
+
//#region src/permissionless/api/oracles/score/run.ts
|
|
5
|
+
/**
|
|
6
|
+
* How a source is told apart from another when two feeds are compared.
|
|
7
|
+
*
|
|
8
|
+
* A named source by its group, since two Chainlink aggregators are one
|
|
9
|
+
* provider failing together. An unnamed one by its contract, since nothing
|
|
10
|
+
* says what else it fails with — so the same contract on both sides is
|
|
11
|
+
* shared, and two different ones are not.
|
|
12
|
+
*/
|
|
13
|
+
function toScoreSource(source) {
|
|
14
|
+
return {
|
|
15
|
+
key: source.identified ? `group:${source.group}` : `contract:${source.target}`,
|
|
16
|
+
name: source.group,
|
|
17
|
+
identified: source.identified
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Every asset of one chain, scored by how its running markets price it.
|
|
22
|
+
*
|
|
23
|
+
* One read of every oracle's feed tree and one of the LP feeds' rate
|
|
24
|
+
* contracts, both multicalls: the score is a reading of the trees and needs
|
|
25
|
+
* no simulation.
|
|
26
|
+
**/
|
|
27
|
+
async function runAssetScores({ client, chainId, slots, compressor, logger }) {
|
|
28
|
+
const running = slots.filter((slot) => !slot.isShutdown);
|
|
29
|
+
const oracles = [...new Set(running.map((slot) => slot.priceOracle.toLowerCase()))];
|
|
30
|
+
const { nodes, unreadableOracles } = await readForest(client, compressor, oracles);
|
|
31
|
+
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`);
|
|
32
|
+
const { targets } = await readRateTargets(client, [...nodes.values()].filter((node) => RATE_FEED_TYPE.test(node.contractType)).map((node) => node.address));
|
|
33
|
+
const { sources } = classifySources(chainId, [...nodes.values()], targets, () => null);
|
|
34
|
+
const byTarget = new Map(sources.filter((source) => source.perturbable).map((source) => [source.target, toScoreSource(source)]));
|
|
35
|
+
const feedOf = (slot) => slot.priceFeed.toLowerCase();
|
|
36
|
+
const reach = sourcesByFeed([...new Set(running.map(feedOf))], nodes, sources);
|
|
37
|
+
const sourcesOf = (feed) => [...reach.get(feed) ?? []].map((target) => byTarget.get(target)).filter((source) => source !== void 0);
|
|
38
|
+
const positions = /* @__PURE__ */ new Map();
|
|
39
|
+
for (const slot of running) {
|
|
40
|
+
const key = `${slot.market.toLowerCase()}:${slot.token.toLowerCase()}`;
|
|
41
|
+
const position = positions.get(key) ?? {};
|
|
42
|
+
position[slot.reserve ? "reserve" : "main"] = slot;
|
|
43
|
+
positions.set(key, position);
|
|
44
|
+
}
|
|
45
|
+
const byAsset = /* @__PURE__ */ new Map();
|
|
46
|
+
for (const { main, reserve } of positions.values()) {
|
|
47
|
+
if (!main || main.isUnderlying) continue;
|
|
48
|
+
const asset = main.token.toLowerCase();
|
|
49
|
+
const entry = byAsset.get(asset) ?? {
|
|
50
|
+
symbol: main.tokenSymbol,
|
|
51
|
+
markets: []
|
|
52
|
+
};
|
|
53
|
+
byAsset.set(asset, entry);
|
|
54
|
+
const mainNode = nodes.get(feedOf(main));
|
|
55
|
+
if (!mainNode) continue;
|
|
56
|
+
if (mainNode.contractType === "PRICE_FEED::ZERO") continue;
|
|
57
|
+
const side = (slot) => ({
|
|
58
|
+
feed: {
|
|
59
|
+
feed: feedOf(slot),
|
|
60
|
+
name: slot.priceFeedName,
|
|
61
|
+
contractType: nodes.get(feedOf(slot))?.contractType ?? ""
|
|
62
|
+
},
|
|
63
|
+
sources: sourcesOf(feedOf(slot))
|
|
64
|
+
});
|
|
65
|
+
entry.markets.push(scoreMarket({
|
|
66
|
+
market: main.market,
|
|
67
|
+
marketName: main.marketName,
|
|
68
|
+
marketConfigurator: main.marketConfigurator,
|
|
69
|
+
curatorName: main.curatorName
|
|
70
|
+
}, side(main), reserve ? side(reserve) : null));
|
|
71
|
+
}
|
|
72
|
+
return [...byAsset].map(([asset, { symbol, markets }]) => {
|
|
73
|
+
const sorted = markets.sort((a, b) => a.score - b.score);
|
|
74
|
+
const worst = sorted[0] ?? null;
|
|
75
|
+
return {
|
|
76
|
+
chainId,
|
|
77
|
+
asset,
|
|
78
|
+
symbol,
|
|
79
|
+
score: worst?.score ?? null,
|
|
80
|
+
unverified: sorted.some((market) => market.unverified),
|
|
81
|
+
worst,
|
|
82
|
+
markets: sorted
|
|
83
|
+
};
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
//#endregion
|
|
87
|
+
export { runAssetScores };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|